(Para efectos prácticos, no se traduce a lo largo del documento la palabra ‘provider’ por ‘proveedor’, por lo que siempre se encontrará la palabra ‘provider’)
En un artículo previo, vimos cómo obtener data desde y hacia nuestros componentes utilizando las anotaciones @Input y el @Output. En este artículo, vamos a ver otro aspecto fundamental de los componentes de Angular: su habilidad para utilizar “providers”. Pordrías haber visto los “providers” en una lista de propiedades que puedes utilizar para configurar componentes y debes haberte dado cuenta de que eso permite que definas un conjunto de objetos inyectables que van a estar disponibles al componente. Eso es bueno, pero por supuesto nos lleva a la pregunta, ¿Qué es un proveedor?
Responder esa pregunta envuelve unas cuantas cosas y nos mete en una discusión sobre el sistema de Dependency Injection (DI) de Angular. Podríamos cubrir DI en otra parte, pero ya ha sido cubierto en otros artículos como este http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html. Vamos a asumir que estás familiarizado con DI y el sistema de DI de Angular 2, en general, como está cubierto en el artículo mencionado, pero, en resumen el sistema de DI se encarga de:
- Registrar una clase, función o valor (class, function o value) Estos ítemes, en el contexto del DI, son llamados “providers” porque dan algo como resultado. Por ejemplo, una clase (class) es utilizada para proveer un resultado en una instancia. Más abajo se verán más detalles sobre los tipos de provider.
- Resolver dependencias entre los providers, por ejemplo, si un provider requiere de otro provider.
- Hacer que el resultado del provider quede disponible en el código cuando este se solicite. Este proceso de hacer que el resultado del provider quede disponible a un bloque de código se llama “inyectarlo”. El código que inyecta el resultado del proveedor se llama “injector.”
- Mantener una jerarquía de inyectores de modo que cuando un componente pregunta por el resultado de un provider desde un provider que no está disponible en su inyector, DI busca la jerarquía de inyectores.
En otro artículo incluimos un diagrama mostrando que los componentes forman una jerarquía comenzando desde el componente root.
Agreguemos ahora los inyectores y los recursos (providers) que ellos registran:
Cada componente tiene su propio inyector que registra los providers. Los inyectores crean inyectores hijos y consultan por un provider que comienza con el inyector local y busca la jerarquía del inyector.
Podemos ver que mientras los componentes forman un gráfico en dirección hacia abajo, sus inyectores asociados tiene una relación bidireccional: los inyectores padre crean hijos (hacia abajo) y cuando un provider es consultado, Angular busca el inyector padre (hacia arriba) si no puede encontrar el provider consultado en el inyector propio del componente. Esto significa que un provider con el mismo identificador en un nivel más bajo sombrea (esconde) al mismo provider en un nivel más alto.
¿Qué son providers?
Así es que, ¿qué son estos “providers” que los inyectores registran en cada nivel? De hecho es muy sencillo: un provider es un recurso o “cosa” de JavaScript que Angular angular utiliza para proveer (al final, generar) algo que queremos utilizar:
- Un provider de tipo class provee/genera una instancia de una clase.
- Un provider de tipo factory genera/provee cualquier cosa que es retornada cuando corre una función específica.
- Un provider de tipo valor no necesita tomar una acción para proveer el resultado como las dos anteriores, simplemente retorna su valor.
Desafortunadamente el término “provider” o proveedor es utilizado para referirse a las 3 y la cosa que resulta del provider: una instancia de una clase, el valor que retorna una función o el valor retornado.
Veamos cómo podemos agregar un provider a un componente al crear la clase provider utilizando MyClass, una clase simple que generará la instancia que queremos utilizar en nuestra aplicación.
Una clase simple con cuatro propiedades.
Ok, esa es la clase; ahora hagamos que Angular la utilice para registrar un provider de tipo class de modo que podamos preguntarle al sistema de DI que nos brinde una instancia para utilizarla en nuestro código. Vamos a crear un componente, ProvDemo_01.ts que va a servir como el componente root de nuestra aplicación. Cargamos este componente y ponemos en marcha nuestra aplicación en el bootstrap.ts:
El archivo bootstrap.ts de nuestra aplicación, que instancia el componente root.
Si no se entiende lo de arriba, busque un artículo previo que muestra cómo crear una aplicación simple en Angular. Nuestro componente root se llama ProvDemo y el repositorio contiene varias números de versiones en él. Puedes cambiar la versión que se muestra al actualizar la línea que importa ProvDemo en el ejemplo de arriba. Nuestra primera versión del componente root se ve así:
CompDemo con MyClass importada, agregada al arreglo de providers y utilizada como un Type en los argumentos del constructor.
Agregar el provider MyClass a este componente es sencillo:
- Importar MyClass
- Agregarla a la propiedad providers de @Component
- Agregar un argumento de tipo “MyClass” al constructor
Por debajo, cuando Angular instancia el componente, el sistema de DI crea un inyector para el componente que registra el provider MyClass. Después Angular ve el tipo MyClass especificado como argumento en el constructor y busca el recién registrado provider MyClass y lo utiliza para generar una instancia que le asigna a myClass (con la inicial minúscula “m”).
El proceso de buscar el provider MyClass y generar una instancia para asignarle a “myClass” la hace completamente Angular. Lo hace al aprovechar la sintaxis de TypeScript para conocer qué tipo buscar, pero el inyector de Angular se encarga de buscar y retornar la instancia de MyClass.
Según lo anterior, podrías concluir que Angular toma la lista de clases en el arreglo de “providers” y crea un registro simple utilizado para recuperar la clase. Pero hay un ligero cambio de dirección para que las cosas sean más flexibles. La razón por la que es requerido un cambio de dirección es para ayudarnos a escribir pruebas unitarias para nuestros componentes que tiene providers que no queremos utilizar en el entorno de pruebas. En el caso de MyClass, no hay muchas razones para no utilizarla como tal, pero si MyClass hiciera una llamada a un servidor para recuperar datos, no vamos a querer o no vamos a poder hacer eso en un entorno de pruebas. Para resolverlo, necesitamos tener la posibilidad de sustituir ProvDemo por una To get around this, we need to be able to substitute within ProvDemo por una MyClass simulada que no haga la llamada al servidor.
¿Cómo lo sustituimos? Vamos por todo el código y realizamos los cambios en todas las referencias a MyClass para que quede como MyClassSimulada? Eso no es eficiente y es un patrón pobre para escribir pruebas.
Necesitamos cambiar la implementación del provider sin cambiar el código de ProvDemo. Para hacer esto posible, cuando Angular registra un provider establece un mapa para asociar una clave (llamada “token”) con el provider actual. En nuestro ejemplo anterior, el token y el provider son lo mismo: MyClass. Agregar MyClass a la propiedad providers en el decorador @Component es un atajo para lo siguiente:
providers: [ provide(MyClass, {useClass: MyClass} ]
Lo cual significa “registre un provider utilizando ‘MyClass’ como token (código) para encontrar el provider y establecer el provider a MyClass de modo que cuando solicitemos el provider, el sistema de DI devuelva una instancia de MyClass.” La mayoría de nosotros estamos acostumbrados a pensar en los códigos como números o hileras de caracteres. Pero en este caso, el token o código is la clase misma. Podríamos haber registrado el provider utilizando una hilera de caracteres como token como a continuación:
providers: [ provide(“aStringNameForMyClass”, {useClass: MyClass} ]
¿Y eso en que ayuda a la hora de realizar pruebas? Significa que en el entorno de pruebas podemos sobreescribir el registro del provider efectivamente del siguiente modo:
provide(MyClass, {useClass: MyClassMock})
Esto asocia el token o código MyClass con el provider de la clase MyClassMock. Cuando nuestro código le pidió al sistema de DI inyectar MyClass en pruebas, obtenemos una instancia de MyClassMock la cual puede simular los datos de llamada. El efecto neto es que todo nuestro código permanece igual y no tenemos que preocuparnos sobre si las pruebas unitarias van a llamar a un servidor que no existe en el entorno de pruebas.
Inyectando Providers no de Clase (Non-Class)
Anteriormente, inyectamos nuestra instancia del provider de clase a un constructor cuando escribimos:
constructor( myClass: MyClass ) {...}
TypeScript nos permite especificar que el parámetro myClass debe ser de tipo MyClass y el sistema de DI realiza el trabajo para instanciar MyClass.
¿Pero cómo le indicamos a Angular inyectar el resultado de nuestro provider si utilizamos un token de hilera de caracteres en vez de una clase? Editemos el archivo bootstrap.ts para agregar un nuevo valor de provider y registrarlo utilizando un token de hilera de caracteres. Recuerda que los providers de tipo value son un tipo de proveedor que retorna el valor asociado a el token. En el ejemplo anterior le indicamos a Angular registrar un provider al agregarle a la propiedad providers de @Component, pero también podemos registrar providers al pasarlos dentro de la función bootstrap del siguiente modo( lo mismo podría ser agregado a la propiedad providers):
bootstrap.ts con un provider de tipo value agregado.
Aquí le agregamos un provider al invocar la función provide y le pasamos un token de hilera de caracteres ( “SECURITY_KEY”) y un objeto que especifica que queremos crear un provider de tipo value y el provider en sí mismo: en este caso un simple valor. Ahora, debemos inyectar el valor generado por el provider de tipo value en nuestro constructor, pero esto no va a funcionar …
constructor( SECKEY: “SECURITY_KEY”) {...}
…por que “SECURITY_KEY” no es un tipo. Para hacer posible la inyección de providers con tokens que no son de clase, Angular nos brinda el decorador de parámetros @Inject. Como con todos los demás decoradores, necesitamos importarlo y después utilizarlo para indicarle a Angular que inyecte un provider asociado con nuestro token de hilera de caracteres. Para lograrlo, ajustamos ProvDemo_02.ts:
Se importa el decorador “Inject” y se utiliza para inyectar un provider de tipo value identificado por medio de un token de tipo hilera de caracteres.
Podríamos utilizar la misma sintaxis para inyectar el provider de tipo MyClass
constructor( @Inject(MyClass) myClass, @Inject('SECURITY_KEY') SECKEY ) {...}
Ok, ya vimos cómo registrar y utilizar providers, pero aprendamos un poco más sobre lo que los providers devuelven.
Providers y Singletons
Como vimos anteriormente, los providers son responsables de generar la cosa que es inyectada. Un provider de tipo clase genera una instancia y la instancia es inyectada. Pero es importante comprender que no vas a obtener una nueva instancia cada vez que el provider de tipo clase sea inyectado. En lugar de eso, el sistema de DI genera la instancia una sola vez, la cachea y cada inyección subsecuente recibe la misma instancia mientras utilices el mismo provider.
Esto último es importante porque cada componente obtiene su propio inyector con sus propios providers registrados. MyClass tiene una propiedad time establecida por la hora actual en milisegundos y un número aleatorio para ayudarnos a ver si estamos recibiendo la misma instancia siempre. Vamos a agregarle un componente hijo llamado ChildComp a nuestra aplicación.
ChildComp con MyClass inyectada en el constructor
Nótese que importamos MyClass y la usamos para establecer el tipo en la lista de parámetros del constructor. Importante: el único propósito de la importada MyClass en ChildComp es como un token que el sistema de DI utiliza, para buscar un provider registrado. Como ChildComp no tiene su propio provedor registrado utilizando ese token, Angular busca en la jerarquía de inyectores para encontrar uno. Para que esto funcione, necesitamos agregar a ChildComp al componente ProvDemo:
Figure 8: ProvDemo con ChildComp agregado a la plantilla (Template).
Importamos ChildComp, lo agregamos a la propiedad directives de @Component para indicarle a ProvDemo que vamos a utilizar el componente ChildComp y vamos a agregar el elemento ChildComp a la plantilla. Cuando la aplicación corra, los mensajes de la consola mostrarán que ProvDemo y ChildComp reciben la misma instancia de MyClass:
ProvDemomyClass 1453033148406 390
ChildCompmyClass 1453033148406 390
Ahora, modifiquemos ChildComp para agregar un provider de tipo MyClass a su inyector:
ChildComp with its own MyClass provider defined.
Todo lo que hemos hecho ha sido agregar la propiedad providers a la anotación @Component. Ahora son creadas dos instancias diferentes de MyClass:
ProvDemomyClass 1453033681877 263
ChildCompmyClass 1453033681881 761
Esta característica de Angular le da mucha flexibilidad sobre los resultados generados por cualquier provider y aun cuando vamos a trabajar con una sola instancia o instancias múltiples. Por ejemplo, podrías insertar un componente en un ciclo, de modo que el componente se genere varias veces. Si este componente repetido registra su propio provider, cada uno obtiene providers únicos. Pero si solamente registras un provider en el componente, cada instancia repetida va a compartir el provider del padre.
Resumen
En este artículo hemos definido lo que es un provider y hemos cubierto 3 tipos diferentes de provider. Después vimos cómo puedes registrar un provider en un componente e inyectar el resultado generado por el provider en el componente. También vimos cómo la jerarquía de los inyectores es utilizada por Angular para encontrar un provider solicitado. Angular te da control adicional sobre cómo el sistema de DI trabaja y dónde debe buscar sus providers pero lo anterior debería hacerte comenzar a crear y a trabajar con providers en tus aplicaciones Angular.
Comentarios
Publicar un comentario