Tutorial de AngularJS #4

La cuarta parte del Tutorial tendra como principal objetivo la comunicación entre controladores y la creación de nuevas directivas.

En el capítulo anterior logramos guardar nuestras imágenes usando la funcionalidad localStorage de HTML5 usando formato JSON, sin embargo al momento de guardar esa imagen debíamos actualizar el explorador usando F5 para ver que la imagen había sido agregada a la lista. Si no se acuerdan, acá esta el resultado del capítulo anterior:

Para resolver este problema menor vamos a aprender a conectar controladores, permitiendo que el MenuController se conecte con el GifListController para que actualice la lista.

Otro punto que nos falta por completar es la forma en la que agregamos elementos a la lista. Para esto usaremos un diálogo con un formulario creado con Bootstrap, y como no queremos reinventar la rueda, instalaremos las directivas necesarias para usar Bootstrap con AngularJS usando ng-bootstrap.

Conectando controladores

Una de las grandes fortalezas de AngularJS es que permite dividir el desarrollo mediante controladores, y esto no necesariamente nos obliga a distribuir las funcionalidades en archivos diferentes.

Por ejemplo: Agregar un Gif, Buscar una imagen y mostrar un diálogo, son funcionalidades que pueden estar en diferentes archivos ya que funcionan distitno. Sin embargo, basta solo con dividir las funcionalidades en controladores independientes y conectarlas entre sí usando las funciones emit y broadcast, que son parte de la variable $rootScope.

La variable $rootScope provee la separación entre el modelo y la vista, contiene las funciones necesarias para interactuar con la vista a medida que la modificamos en el controlador. Toda aplicación tiene una única raiz, llamada $rootScope y de ella dependen todos los otros scopes.

Entonces, si se dan cuenta, todo se trata de los scopes, pero, ¿qué es un scope?
Diremos que un scope es la definición de un contexto que engloba variables y expresiones asociadas, en este caso, a un controlador. Es decir, un scope es una forma de definir el ámbito de cada controlador, y sobre cada ámbito existe este $rootScope, que puede intervenir a todos los scopes “hijos”.

El otro ingrediente que debemos conocer es que AngularJS modifica el flujo de proceso del explorador, agregando un ciclo adicional que procesa cada evento y modifica el DOM. Nos abre la posibilidad a modificar el DOM después de haber cargado la página, como ya han experimentado, y abre la posibilidad de usar la API de eventos desde la variable $rootScope.

Aquí es donde $broadcast y $emit hacen su aparición, ya que estas variables nos permiten enviar eventos después de que la página haya sido cargada, para luego recibir esos eventos usando el método $on.

La forma de usar $broadcast y $emit dependen de como queremos comunicar nuestros controladores, para esto, existen 3 opciones:

  1. Para comunicar un controlador padre con un controlador hijo se utiliza $broadcast
    function padreCtrl($scope){
        $scope.$broadcast('evento', [1,2,3]);
    }
    
    function hijoCtrl($scope){
        $scope.$on('evento', function(event, data) { console.log(data)});
    }
  2. Para comunicar un controlador hijo con un controlador padre se utiliza $emit
    function padreCtrl($scope){
        $scope.$on('evento', function(event, data) { console.log(data)});
    }
    
    function hijoCtrl($scope){
        $scope.$emit('evento', [1,2,3]);
    }
  3. Mientras que para comunicar controladores sin relación alguna, se utiliza $rootScope.$broadcast
    function indexCtrl($rootScope){
        $rootScope.$broadcast('evento', [1,2,3]);
    }

Ahora, usando esta explicación, encontraremos que los controladores que necesitamos conectar no tienen una relación padre-hijo, por ende, usaremos la tercera opción. Para esto, agregaremos al MenuController el evento “reloadList” que será leido por el GifListController para actualizar la lista:

$scope.add = function() {
    var url = prompt('Ingrese una URL');
    if (url != null) {
        var image = {
            'name': 'Ejemplo',
            'url': url,
            'tags': [],
            'favorite': false
        };
        Storage.save(image);

        $rootScope.$broadcast('reloadList');
    }
};

Aquí, agregamos el evento “reloadList” que es totalmente inventado por nosotros. Luego, en el controlador de la lista, leemos el evento usando $on.

$rootScope.$on('reloadList', function(event, data){
    $scope.giflist = Storage.list();
});

Acá, tomamos el evento con el método $on y recargamos la lista. Recuerden que debemos inyectar la variable $rootScope en ambos controladores para poder usar la variable. El ejemplo aplicado de estas modificaciones deberían dar el siguiente resultado:

ng-bootstrap

La siguiente parte es más simple, se trata de implementar un plugin que nos hará la vida un poco más fácil. Si bien Bootstrap nos entrega una gran variedad de funcionalidades, éstas no son compatibles con AngularJS ya modifican el DOM justo después de ser cargado, y esto no siempre es así. Como AngularJS modifica el flujo de Javascript, puede que los plugins no ejecuten en el momento correcto, y es por eso que necesitamos modificarlo usando directivas (directives).

Es por eso que necesitamos este plugin, porque necesitamos implementar las directivas que controlen los elementos del DOM relacionados a Bootstrap. Para esto, iremos a la siguiente URL: http://angular-ui.github.io/bootstrap/ y descargaremos la última versión minificada. Este plugin contiene todas las directivas programadas y listas para nuestra función, solamente nos basta leer la documentación y darles un uso.

El primer paso será descargar la última versión del plugin y agregarla a nuestro header, dejándolo en el directorio “javascripts”:

<script src="javascripts/ui-bootstrap-tpls-0.11.0.min.js"></script>

Después, debemos agregar el módulo “ui.bootstrap” dentro de nuestra aplicación, inyectándolo en la variable gifwalletApp.

var gifwalletApp = angular.module('gifwalletApp', ['ui.bootstrap']);

Ahora, necesitamos leer la documentación que entrega el sitio respecto al uso de la función “modal”. En el ejemplo que entregan nos da la posibilidad de usarlo de distintas formas, nosotros lo haremos así:

  1. Crearemos la interfaz del modal en un nuevo archivo llamado add_gif.html con el el siguiente contenido:
    <div class="modal-header">
        <h3 class="modal-title">Agregar GIF</h3>    
        <p class="text-muted">Añade un gif favorito a tu wallet usando el nombre y la URL</p>
    </div>
    <div class="modal-body">
        <form action="" role="form">
            <div class="form-group">
                <label for="name">Nombre</label>
                <input type="text" id="name" 

    Si revisamos el código con atención veremos que agregamos la funcionalidad cancel y save a los dos botones del footer. Mientras que a cada elemento del formulario, le definimos un atributo de modelo usando “ng-model” para poder obtener el valor modificado y guardar la información en la variable “Gif“.

  2. Modificaremos la función “add” que se encuentra en el MenuController para que abra un modal usando los atributos templateUrl y controller. Para eso, agregamos al controlador la variable $modal que contiene la funcionalidad.
    gifwalletApp.controller('MenuController', ['$rootScope', '$scope', 'Storage', '$modal',
        function($rootScope, $scope, Storage, $modal) {

    Al agregar esta variable, podremos utilizarla dentro del método add que le da la funcionalidad a nuestro botón +

    $scope.add = function() {
        $modal.open({
            templateUrl: 'add_gif.html',
            controller: function($scope, $modalInstance) {
                $scope.save = function() {
                };
    
                $scope.cancel = function() {
                    $modalInstance.dismiss('cancel');
                };
            }
        });
    };

    El método $modal pide como parámetro el templateUrl que es donde está la interfaz del template. En el ejemplo del sitio, se agrega el template en línea usando <script type="text/ng-template" id="...">, para nuestro caso usaremos el archivo externo para separar las interfaces.
    Del mismo modo, el controlador que determina el funcionamiento del modal se pide en el parámetro controller, que en el ejemplo es definido en una variable externa. En nuestro caso, lo hicimos en línea pues no existe una mayor complejidad.

  3. El último paso es agregar la funcionalidad anterior al método save del siguiente modo:
    controller: function($scope, $modalInstance) {
        $scope.Gif = {};
  4. Primero definimos la variable $scope.Gif$scope. Luego, tomamos el mismo método anterior y le definimos como parámetros los nombres de los atributos utilizados en el modal:
    $scope.save = function() {
        var image = {
            'name': $scope.Gif.name,
            'url': $scope.Gif.url,
            'tags': [],
            'favorite': false
        };
        Storage.save(image);
    
        $rootScope.$broadcast('reloadList');
        $modalInstance.dismiss('cancel');
    };

    Acá, la única diferencia es que estamos agregando el método “dismiss” para cerrar el diálogo luego de agregar el elemento a y actualizar la lista.

Como siempre, el ejemplo funcionando lo pueden ver acá:

Este ejemplo difiere de nuestro código en que el template del modal está en un tag script dentro del header, que es tán válido como dejarlo en otro archivo. ¿Quieren probar? Intenten con este gif: http://media.giphy.com/media/LWThj4XwChQiY/giphy.gif

Y eso sería todo con el cuarto episodio de este Tutorial. Les doy las gracias a quienes han seguido este tutorial y de pasada, las diculpas ya que no había tenido tiempo para poder continuarlo. El próximo capítulo veremos a fondo cómo funcionan las directivas agregando funcionalidad a los favoritos, tags y eliminación de Gifs.

¡Espero sus comentarios!

Tutorial de AngularJS #5

Blog, Tutorial, Tutorial Javascript
6 minutos