Recientemente leí este artículo de mi buen amigo Norman Coloma.

En él, introduce una mejora en el uso de validadores asíncronos con respecto a muchos de los ejemplos que verás por internet, llevándose su lógica a un servicio externo.

Aún así, y tras hablarlo con él, vimos que ese componente era demasiado listo:

  • ¿Por qué saber de servicios externos?
  • ¿Por qué saber de controles abstractos?
  • ¿Por qué hacer ese bind a la hora de validar?
  • ¿Por qué no introducir un validador asíncrono de una manera tan fácil como hacemos con los síncronos?

Normalmente, una validación asíncrona implica una comunicación con el servidor, lo que supone inyectar el servicio HttpClient, o alguna clase que dependa de él. Esto hace que el uso de validadores asíncronos sea un poco más complejo. Qué bonito sería si pudieramos utilizar el mecanismo de inyección de dependencias para traernos los validadores asíncronos, ¿no? Y la casualidad es que se puede.

Nuestro ejemplo: una función de validación de emails

Vamos a hacer una función que valide que un email no está siendo utilizado. Para ello, necesitaremos el servicio HttpClient de Angular para consultar a una API si un email está disponible. No nos fijemos mucho en la implementación. Cogí la primera API pública que se me ocurrió 🙂

Al igual que muchos otros frameworks, Angular nos proporciona mecanismos para la inyección de dependencias. Gracias a él, podemos inyectar clases de una forma muy sencilla: declarando una clase y añadiéndola a la lista de providers de los metadatos de nuestro módulo o componente, como hizo Norman:

Además, con el mismo mecanismo podemos también inyectar elementos que no son clases como interfaces (además, el concepto de interfaz no existe como tal en JavaScript) o funciones. Esto nos viene muy bien para nuestra función asíncrona de validación.

Para ello, debemos tener claros dos conceptos: los Factory Providers y los Injection Tokens.

Imagino que si ya has tocado Angular estarás mínimamente relacionado con el concepto de Provider. Viene a ser el valor concreto de una dependencia, que instanciará el inyector la primera vez que alguien lo necesite.

La manera más común y sencilla de ver un Provider es en forma de clase.

Nuestra función de validación de emails tiene dependencia con una librería de Angular, con lo cual no es una librería de utilidades independiente del framework. Vamos, que necesitamos inyectarle un elemento que se inicializa dentro del ciclo de vida de la aplicación. Para este caso vienen muy bien los Factory providers. Un FactoryProvider implementa la siguiente interfaz:

Imagino que quedará claro que el valor de useFactory corresponde a la función de validación definida previamente. El atributo deps hace referencia a todas las dependencias de la factoría. En nuestro caso, el ya mencionado HttpClient.

Cuando trabajamos con clases, el valor otorgado al atributo provide puede ser la propia clase. Pero cuando lo que queremos definir no es una clase debemos hacer uso de los Injection Tokens.

Un InjectionToken es un valor único que daremos a nuestro provider para que el inyector pueda gestionarlo.

Una vez conocemos todas las “patitas” que necesita nuestro provider, vamos a ver cómo quedaría bien montado:

No sé vosotros, pero extrayéndolo así yo lo veo bastante fácil de testear, ¿no creéis? Sería muy fácil hacer un mock del HttpClient y un par de test unitarios para explorar todas las posibilidades.

¿Y cómo lo utilizo?

Una vez hemos definido nuestro provider, podemos hacer uso de él allá donde lo necesitemos gracias al decorator @Inject. Utilizándolo en el constructor de cualquier componente inyectable en conjunción con el InjectionToken que hayamos creado, podremos utilizar nuestro validador.

Así, nuestro componente únicamente es responsable de saber qué elementos le pasará al formulario:

Podemos ver un ejemplo funcionando en este Plunker

Espero que este artículo os haya servido. Últimamente estoy haciendo las cosas de otra manera para desacoplar mi código de Angular. Para ello (y entre otras cosas) en lugar de inyectar clases directamente utilizo interfaces, y en este escenario también se hace esencial el uso de InjectionTokens.

Anuncios