Introducción a Angular JS

Evaluar expresiones con el servicio $parse Más videos

Descripción del tema

En el tutorial anterior vimos como crear una directiva y como utilizar el método $watch para monitorizar los cambios sobre una propiedad en el scope. En este tema veremos como recuperar el valor de la opción seleccionada en el combobox para poder usarlo en el controlador. También veremos como asignar el valor del select de manera dinámica para cuando se quiera editar un registro existente.

Removiendo el ngModel

Originalmente había planeado utilizar la directiva ngModel dentro de nuestra directiva, inclusive en los tutoriales anteriores la requerimos y también la definimos en nuestro template. Pero sucede que el ngModel en conjunto con el select, generan conflictos con nuestra directiva y suceden algunos errores difíciles de debuguear.

Esto es porque la directiva ngModel y nuestra directiva están modificando el DOM del select al mismo tiempo, por lo tanto las cosas se desincronizan y hay problemas cuando se selecciona una opción.

Para evitar problemas vamos evitar usar el ngModel, vamos a borrarlo de nuestro template y de nuestra directiva. Este cambio por otro lado es bueno, ya que conoceremos una técnica avanzada para asignar el valor al modelo de la categoría. El código del template lo cambiaremos por lo siguiente.

<select bootstrap-select="categories" select-value="id" select-label="name" select-model="bookmark.category" class="form-control"></select>

Ahora usaremos un atributo llamado select-model para indicarle a la directiva donde asignar el valor seleccionado.

El código de la directiva debe quedar algo semejante a esto.

.directive('bootstrapSelect',function(){

    return {
        link: function (scope, element, attrs) {
            var collection = attrs.bootstrapSelect,
                valueProperty = attrs.selectValue,
                labelProperty = attrs.selectLabel,
                model = attrs.selectModel; //<------
                
            $(element).selectpicker();
            
            scope.$watch(collection,function(data){
                if(data){
                    $(element)
                        .find('option')
                        .remove();

                    var html = [];
                    $.each(data, function(index, object) {
                        html.push('<option value="'+object[valueProperty]+'">');
                        html.push(object[labelProperty]);
                        html.push('</option>');
                    });
                    $(element).append(html.join(''));
                    $(element).selectpicker('refresh');
                }
            });
        }
    }
})

Solamente quitamos la propiedad require, el parámetro ngModel del los parámetros que recibe la función link y ahora usamos el atributo selectModel para sacar la propiedad que nos interesa.

Editar un Registro

Actualmente cuando editamos un bookmark el combobox no está desplegando la categoría correcta, siempre despliega la primera opción; también cuando agregamos un nuevo bookmark la categoría no se selecciona de manera adecuada.

Lo primero que necesitamos hacer para solucionar esos problemas es agregar un listener a la propiedad bookmark.category, para esto utilizamos el método $watch que ya conocemos. Dentro de la directiva que ya definimos agregamos el siguiente código.

scope.$watch(model,function(data){
    console.log(data);
});

La variable model la estamos sacando de los atributos que definimos en la directiva, en este caso model contiene el bookmark.category.  Revisa el tutorial anterior si este paso no te queda muy claro.

Si ejecutamos nuestra app y miramos la consola, observaremos que la primera vez es indefinido, pero tan pronto como abrimos la ventana del formulario el valor cambia, ahora recibimos el objeto de la categoría que debemos seleccionar. Vamos a seleccionar la opción en el combobox de la siguiente manera.

scope.$watch(model,function(data){
    if(angular.isObject(data)){                             //Step 1
        $(element).selectpicker('val',data[valueProperty]); //Step 2
    }
});
  1. En el paso uno revisamos que el nuevo valor a asignar sea un objeto, esto para evitar asignar valores nulos o indefinidos.
  2. En el paso dos utilizamos el método selectpicker para seleccionar el valor del combobox, por medio del atributo valueProperty es que podemos sacar el ID de la categoría para seleccionar la opción correcta.

Con eso es suficiente para que el combo se seleccione correctamente cuando agreguemos un nuevo bookmark o bien cuando editemos un bookmark existente.

Asignar el valor seleccionado al scope del controlador

Seleccionar el valor existente fue realmente sencillo, lo interesante viene cuando tenemos que asignar el valor seleccionado en el combobox a la propiedad bookmark.category en el scope del controlador, originalmente pensaba utilizar el ngModel y sería extremadamente sencillo, pero por ciertos conflictos lo haremos manualmente.

Angular cuenta con un servicio llamado $parse que es utilizado para evaluar expresiones dentro de un scope, es una utilería no muy conocida por muchos pero realmente muy conveniente. Vamos a agregar el siguiente código a la directiva.

.directive('bootstrapSelect',function($parse){   //Step 1

    return {
        link: function (scope, element, attrs) {
            var collection = attrs.bootstrapSelect,
                valueProperty = attrs.selectValue,
                labelProperty = attrs.selectLabel,
                model = attrs.selectModel,
                getter = $parse(model),         //Step 2
                setter = getter.assign;         //Step 3

            //...
        }
    }
})
  1. En el primer paso inyectamos el servicio $parse, esto nos permitirá utilizarlo dentro de nuestra directiva.
  2. En el segundo paso generamos el getter para poder acceder al modelo que estamos enviando como atributo, en este caso sería bookmark.category.
  3. El paso tres es el que más nos interesa, ya que aquí creamos el setter para poder asignar el nuevo valor al modelo que recibimos en los atributos.

El servicio $parse recibe un string con la expresión a evaluar, en este caso necesitamos tener acceso de escritura y lectura al modelo que definimos en los atributos.

Por último vamos a definir el evento change al select, para que cada que cambie de valor modifiquemos el modelo que estamos usando. Aquí es donde la magia sucede.

$(element).change(function(){                //Step 1
    var col = scope[collection],
        val = $(element).val();              //Step 2

    for(var i=0,len=col.length;i<len;i++){
        if(val == col[i][valueProperty]){    //Step 3
            setter(scope,col[i]);            //Step 4
            break;
        }
    }
});
  1. En el paso uno mediante jQuery asignamos el evento change, este se disparará cada que el usuario seleccione una opción del combo.
  2. En el paso dos tomamos el nuevo valor seleccionado, esto nos regresará el ID de la categoría.
  3. En el paso tres buscamos la categoría seleccionada en la colección que tenemos en el controlador, aquí estamos usando el atributo valueProperty para hacerlo dinámico y reutilizable.
  4. Finalmente asignamos el nuevo valor al modelo, es necesario pasarle el scope y el valor que se le asignará, con esto es suficiente para que se actualice el modelo bookmark.category en este caso.

Ya hemos terminado nuestra directiva, podemos agregar un nuevo bookmark sin problema así como editar alguno existente. Esta directiva la podemos usar cuantas veces necesitemos ya que estaremos enviando parámetros diferentes. 

Evaluando expresiones con el servicio parse

Evaluando expresiones con el servicio $parse

Conclusión

Crear directivas desde cero puede ser complicado, sobre todo si no estamos familiarizados con Angular. En estos últimos tres tutoriales he mostrado como integrar un plugin de jQuery mediante una directiva, es importante tener en cuenta como funciona el Digest Cycle, también debemos mantener el uso moderado de los $watchers, ya que si definimos muchos, nuestra aplicación se verá afectada en el rendimiento.

Puedes usar el foro para dejar tus dudas y recuerda compartir este tutorial en tus redes sociales, con esto me ayudarás a seguir publicando contenido de manera gratuita.

Te gustaría recibir más tutoriales como este en tu correo?

Este tutorial pertenece al curso Introducción a Angular JS, te recomiendo revises el resto de los tutoriales ya que están en secuencia de menor a mayor complejidad.

Si deseas recibir más tutoriales como este en tu correo te recomiendo registrarte al curso, si ya eres miembro solo identifícate y registrate al curso, si no eres miembro te puedes registrar gratuitamente!

Si no gustas registrarte en este momento no es necesario! Aún así puedes recibir los nuevos tutoriales en tu correo! Jamás te enviaremos Spam y puedes cancelar tu suscripción en cualquier momento.

¿Olvidaste tu contraseña?

4Comentarios

  • Avatar-12 Jorge Armand Olivero 20/11/2014

    Bacano ¿Una pregunta para resivir la peticiones http estas implementando laravel ?

    • Avatar-6 Crysfel Villa 20/11/2014

      No, el API está hecha en Ruby on Rails :) pero realmente, puedes usar cualquier lenguaje del lado del servidor, siempre y cuando expongas la información en servicios JSON. Saludos

    • Avatar-1 pincho3xxx 18/06/2015

      Hola, estoy siguiendo tu curso con gran interes, te quería preguntar como habría que hacer para que la directiva funcionara para varios items, por ejemplo que un bookmark, tuviera varios categories. Gracias.

      • Avatar-4 arnold coba ura 04/02/2016

        Hola, no habias dicho en los videos de ajax que cambiarias esta linea ya que no es una buena practica modificar el dom en angular como seria ? $('#bookmarkModal').modal('hide');

        Instructor del curso

        Crysfel3

        Autor: Crysfel Villa

        Ha participado en varios proyectos con Angular y es participante activo del grupo de Angular NYC.

        Descarga Código Fuente Ver Demostración

        Regístrate a este curso

        Este tutorial pertenece al curso Introducción a Angular JS, revisa todos los tutoriales que tenemos en este mismo curso ya que están en secuencia y van de lo más sencillo a lo más complicado.

        Tendrás acceso a descargar los videos, códigos y material adicional.

        Podrás resolver los ejercicios incluidos en el curso así como los Quizzes.

        Llevarás un registro de tu avance.