cómo hablan las cosas de angular entre sí (2)

Comunicación entre los controladores y las vistas

Francis Picabia

archivado en: JavaScript / 31 mayo, 2015 / taller:

Sigo explicando cómo se comunican los distintos tipos de archivos de angular (1.*). En esta entrada veremos cómo se relacionan los controladores con las vistas, un proceso que funciona muy bien hasta que empiezas a trabajar con javaScript y librerías varias no angularescas.

Como es sabido, los controladores se conectan con sus respectivas vistas mediante el $scope, un objeto al que podemos ir agregando propiedades que podemos trabajar desde los dos sitios. Por ejemplo, si tenemos esta propiedad foo definida en el controlador:

$scope.foo = "Haga Basin";

En la vista podemos recuperarla en cualquier expresión angular o con el sistema de llaves...

mi propiedad es {{ foo }}

<div ng-if="foo='Haga-Basin'>...

Es el llamado doble bindeo y que funciona más o menos así. Cuando se carga el controlador, angular va «guardando» todas las propiedades agregadas, definidas, en el $scope en una cosa que vamos a llamar colección y prepara una especie de escuchas o watches. Cuando suceden determinados eventos, angular recorre esa colección y va comparando el valor antiguo de cada propiedad con el que tiene en ese momento y, si alguna propiedad tiene un valor distinto, renderiza de nuevo la vista: es lo que se conoce como fase $digest y que podemos traducir como «ciclo de renderizado».

Estos eventos que pueden provocar un ciclo de renderizado son los ng-eventos de angular -como el ng-click, el ng-change, el ng-keypress, etcétera- y las operaciones asíncronas. Es decir, cuando usamos un $timeout, un $interval o una llamada ajax con $http. Por ejemplo, en esta vista...

<article ng-controller="miControlador">

{{foo}}

</article>

Con este controlador, foo mostrará al principio «Haga Basin», pero a los 3 segundos, cuando concluya la operación asíncrona definida en el $timeout, se lanzará un ciclo de renderizado nuevo y mostrará la cadena «Sietch Tabr».

.controller('miControlador', [ "$scope", "$timeout", function($scope, $timeout) {

$scope.foo = "Haga Basin";

$timeout(function() {

$scope.foo = "Sietch Tabr";

}, 3000);

}])

Ojo, las operaciones asíncronas que desencadenan un ciclo de renderizado son las que están wrapeadas en angularesco, como un $timeout, no las normales de javaScript (setTimeout).

$apply y $digest

El sistema, como decía, funciona muy bien de manera automática en escenarios muy sencillos, pero a la que se complican los procesos, sobre todo si tiramos de javaScript y librerías no angularescas, hay que forzar los renderizados. Imaginemos, por ejemplo, que estamos usando un plugin de jquery que tiene un evento on-click, un caso algo forzado, porque debería ir en una directiva y no en un controlador, pero que es ilustrativo. Algo así en la vista:

<article ng-controller="miControlador" id="awesome">

{{foo}}

</article>

Y así en el controlador:

$('#awesome').click(function() {

$scope.foo = "Sietch Tabr";

});

Como no es un evento angular sino de jquery, está fuera del contexto angularesco y el frame no sabe que debe lanzar un ciclo de renderizado, así que tenemos que indicárselo de forma manual. Hay varias maneras de hacerlo. Una es invocando directamente el método $digest del objeto $scope:

$('#awesome').click(function() {

$scope.foo = "Sietch Tabr";

$scope.$digest();

});

Sin embargo, así se corre el riesgo de lanzar un renderizado cuando ya hay uno en marcha, lo que provoca el célebre error de angular «digest already in progress». Esto se puede prevenir incluyendo un condicional: hazlo solo si no estás en un ciclo de renderizado ($scope.$$phase)...

$('#awesome').click(function() {

$scope.foo = "Sietch Tabr";

if(!$scope.$$phase) {

$scope.$digest();

}

});

Pero este cortafuegos no es una solución óptima, pues corremos el riesgo de que no se cambie el valor de la propiedad en la vista, que es precisamente lo que estamos buscando y, además, nos conduce a un laberinto de fases $digest en el que resulta muy fácil perderse a la que se superan los cuatro eventos. Podemos evitarlo de una manera muy sencilla: cambiando el valor de la propiedad dentro de un $timeout a 0, que recordemos es uno de los eventos asíncronos que dispara el loop de forma natural:

$('#awesome').click(function() {

$timeout(function() {

$scope.foo = "Sietch Tabr";

}, 0);

});

Pero aún hay una forma más elegante de hacerlo y es con $apply, un método de $scope que viene a hacer esto: cuando se acabe el ciclo de renderización, cambia este valor y lanza de nuevo el loop.

$('#awesome').click(function() {

$scope.$apply(function() {

$scope.foo = "Sietch Tabr";

});

});

$watch

Por último contar que también podemos decirle a angular que haga algo cuando cambie el valor de una propiedad del contexto, de la colección, utilizando el método $watch. En cierta manera, equivale a los addEventListener. Por ejemplo, así podríamos hacer algo cuando foo cambiase de valor al ser pulsado el nodo awesome en la vista.

$scope.$watch('foo',

function( newValue, oldValue ) {

if ( newValue !== oldValue ) {

// hacemos algo...

}

}, true);

$('#awesome').click(function() {

$scope.$apply(function() {

$scope.foo = "Sietch Tabr";

});

});

Sin embargo, en general, se desaconseja el abuso de este tipo de $watches porque consumen muchísimo. A mí la verdad es que no me gustan nada y prefiero utilizar funciones normales bindeadas a eventos de la vista, pero bueno, ahí están para casos donde no queda más remedio.

Por hoy lo dejo aquí.

|| Tags: , ,

valoración de los lectores sobre cómo hablan las cosas de angular entre sí (2)

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • 5 sobre 5 (6 votos)

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

2 respuestas a “cómo hablan las cosas de angular entre sí (2)

  1. Bueno, pues felicitaciones por tu blog, y gracias por compartir. He seguido toda esta serie y me parece muy interesante y útil. Tienes un gran dominio del tema y un léxico muy ameno.
    Solo para agradecerte.
    Gracias!