Si intentamos desmitificar este misterioso sistema, veremos que en Angular se habla de un proceso llamado Change Detection. La tarea básica de este proceso, es tomar el estado interno de nuestra aplicación, y reflejarlo de alguna manera en la interfaz de usuario.
Ahora bien, la pregunta del millón es: ¿cuándo Angular debe ejecutar este «costoso proceso» de comprobación? La respuesta es bien sencilla. Debe lanzarlo cada vez que el estado de la aplicación cambie.
Vale, quizás si ponemos esta respuesta en un examen de Angular nos caiga un cero… La pregunta correcta sería ¿cómo detecta Angular que el estado ha cambiado?
Debemos saber que básicamente el estado de una aplicación puede cambiar debido a tres cosas:
- Events –
click
,submit
, … - XHR – Recuperar datos de un servidor externo
- Temporizadores –
setTimeout()
,setInterval()
La naturaleza de todos estos elementos es asíncrona. Así pues, podríamos afirmar que siempre que haya una operación asíncrona, el estado de nuestra aplicación puede cambiar.
Monkey Pathching
Aquí es donde entra en juego el llamado Monkey-Patching. Este término es usado para explicar la manera que tiene un programa, en este caso nuestro framework, de extender o modificar, parte de un código en tiempo de ejecución. Típicamente en el Startup.
¿Qué quiero decir con esto? Pues que Angular tiene monkey-patched las funciones de naturaleza asíncrona. Los que provengan del mundo de AngularJS, se acordarán bien de aquellas funciones: $timeout, $interval, entre otras, que debían usarse en lugar de las nativas.
En las nuevas versiones de Angular, gracias al Monkey-Patching, podemos usar las funciones asíncronas nativas. Total transparencia para el desarrollador. Nada de $timeout’s, $interval’s y otras funciones extrañas. Debido al Monkey-Patching a parte del funcionamiento nativo normal, se lanzará el proceso de Change Detection.
Una vez cargada una app Angular, podemos intentar depurar, a modo de ejercicio, la función setTimeout. Si pulsamos F11 desde las devtools, veremos que la función nos es la nativa y está monkey-patched.
Explicar cómo están «trucadas» estas funciones asíncronas, queda fuera del alcance de este artículo. Comentar simplemente que se basa en las llamadas Zones. Podríamos decir que son algo parecido a un contexto de ejecución. Angular corre dentro de una de estas Zones, y gracias a ellas es capaz de resolver el problema de cuando comienzan y acaban ciertas tareas asíncronas.
Un poco de práctica
Después de la tediosa teoría, me gustaría entrar en algo de práctica, que es lo que nos gusta a los developers. Y lo que en el fondo me impulsó a escribir este post.
Imaginemos que tenemos que controlar la creación y destrucción de un elemento html por medio de una media query de javascript. Para este menester la API nos ofrece un método llamado matchMedia.
Según este código la instancia de nuestro componente <h1>
está ligada a la propiedad flag
. Por defecto está inicializada a true
, y cuando el navegador tenga un tamaño inferior a 1200px, el componente se destruirá.
Y esta es toda la teoría del código, pero si lo probamos, comprobaremos que no funciona.
Para probar los diferentes ejemplos debes abrir la preview en otra ventana clickando en el botón superior derecha de la pantalla. Encoge y agranda el tamaño del navegador.
¡Pues vaya con Angular, pronto empezamos a caerle mal! Tranquilidad, que no cunda el pánico, o sí… ¿Qué es lo que está pasando aquí? Pues lo que ocurre, es que no todos los métodos de la API de javascript que trabajan de forma asíncrona, están monkey-patched. Como es el caso de matchMedia y su addListener.
¿Y que podemos hacer ahora? Angular nos provee una utilidad que permite lanzar manualmente el Change Detector, comprobar si algo ha cambiado y refrescarlo en la vista.
¿Y si rizamos un poco el rizo?
Vamos a intentar algo sencillo, pero curioso. ¿Qué ocurriría si añadimos un botón dentro de nuestro componente con un evento de click? El siguiente código lo muestra.
- Haz click en el botón. La instancia desaparecerá.
- Encoge y agranda en navegador para que aparezca la instancia nuevamente.
- Prueba a hacer click en el botón otra vez.
¡¡¡Tachaaaan!!! ¡¡No funciona, esta roto!! Aquí es cuando algunos empiezan a odiar Angular y se cambian de framework ¿Pero qué demonios está pasando ahora?
Al agrandar el navegador se modifica el flag
y se lanza manualmente el Change Detector. La instancia se crea, pero se crea fuera del contexto de Angular. Fuera de una NgZone. Por lo que la instancia y en este caso su evento click
también se encuentran fuera del contexto.
Al hacer click en el botón, se ejecuta el método onClick
correctamente. Pero Angular no es capaz de detectar que el callback ha terminado y, lanzar el proceso de detección de cambios. A la hora de añadir Angular el binding al evento, se podría decir que lo ha hecho con el método nativo y, no con el método monkey-patched.
Para solucionarlo podríamos hacerlo de la manera anterior a través del método this._cd.detectChanges()
. Pero acabaríamos ensuciando nuestro bonito código con muchas llamadas de este tipo.
Una mejor solución, es decirle a Angular que ejecute dentro de su contexto, lo que está fuera de él, a través del método run
.
Basta con envolver el código, hasta ahora ejecutado fuera del contexto de Angular, en una NgZone. La instancia del componente se creará dentro del contexto, incluidos sus eventos y bindings. Angular ahora será notificado gracias al Monkey-Patching, de cuando el callback termina su ejecución y, será capaz de lanzar el proceso de Change Detection automáticamente.
Conclusión
Manejar lo básico de las nuevas versiones de Angular es fácil. Pero, reconozco que a un nivel más avanzado, la curva de aprendizaje se complica un poco. La buena noticia es que hemos intentado examinar algo de lo más complejo que tiene Angular. El proceso de Change Detection. Y yo espero haber resuelto un poco de ese misterio con este post.