Introducción a las directivas de AngularJS

Las directivas podrían equipararse con etiquetas HTML, son etiquetas enriquecidas. Con AngularJS podemos definir directivas personalizadas con una complejidad tan profunda como queramos y tan simples de usar como una etiqueta común. Las configuraciones pueden versar en el estilo (CSS), el contenido (HTML) y el comportamiento (Javascript). De manera similar a cualquier desarrollo web, pero con la capacidad de encapsular esta configuración en un único elemento y reutilizarlo de una manera muy sencilla.

AngularJS ya incluye por defecto un gran número de directivas predeterminadas que cubren las necesidades básicas comunes a casi todos los proyectos. En la documentación oficial podemos encontrar el listado completo de todas ellas. Sin embargo son pocas comparadas con las que nos podemos encontrar creadas por organizaciones o desarrolladores independientes. Las directivas es la forma más común de incorporar plugins personalizados externos en una aplicación de AngularJS.

De hecho en este sentido serían equiparables a los widgets de jQueryUI. Ambos definen una fórmula para crear estructuras complejas de una manera unificada y re-utilizable. Sin embargo, mientras que en jQuery la estructura de HTML para incorporar un widget es al uso y toda la carga reside en el script, en Angular es al revés. Las directivas se declaran y configuran en el HTML, y el script únicamente presenta las variables o funciones a las que accede la directiva.

Podamos usar directivas predeterminadas o desarrolladas por terceros. Pero la verdadera potencia de este componente se alcanza combinando varias directivas existentes o creando las nuestras propias para cubrir las necesidades concretas de nuestro sistema.

Directivas predeterminadas de AngularJS

Entre las directivas que ofrece Angular en su última versión, las más básicas son:

  • ng-app: Contiene una aplicación Angular. Es necesaria para que el motor de este framework esté activo en la sección del DOM donde se coloca. Suele colocarse en el dom completo (<html ng-app=”my-app”> …</html>). Se le asigna un atributo que será el nombre del módulo aplicación.
  • ng-controller: Instancia un controlador para esa sección del DOM. Recibe como atributo el nombre del controlador. Permite a este acceder y modificar el scope dentro de esa sección.
  • ng-include: Esta directiva recibe como parámetro un template HTML y lo renderiza dentro de su etiqueta. Este nuevo template heredará el scope de la directiva.
  • ng-bind: Sustituye un elemento por el contenido una variable del scope, y cuando cambie uno cambiará el otro. El atributo de la directiva es el nombre de la variable. La forma abreviada de utilizar esta directiva es con dobles corchetes:

  • ng-model: Conecta (bindea) un elemento, como puede ser un input, con una variable del scope. El contenido de esta variable será también el valor del input en todo momento. En este caso la conexión es bidireccional dado que el input es editable.
  • ng-init: Ejecuta una expresión – la que recibe como parámetro – en el momento de renderizado del elemento.

Para procedimientos algo más concretos nos encontramos otras directivas muy útiles, como por ejemplo las siguientes:

  • Condicionales y bucles:
    • ng-switch / ng-switch-when: Muestra un elemento u otro.
    • ng-show / ng-hide: Muestra u oculta un elemento según un booleano. Sólo juega con el display del elemento (CSS)
    • ng-if: Renderiza o no un elemento según un booleano. En lugar de aplicar display: none, lo elimina del DOM directamente.
    • ng-repeat: Repite un elemento tantas veces como elementos en un array.
  • Eventos (todos reciben como parámetro un callback):
    • ng-click / ng-dblclick: Se ejecutará al clicar una o dos veces en el elemento.
    • ng-change: Se ejecuta cuando se modifica el valor (ng-model) de un input.
    • ng-focus / ng-blur: Al poner el foco en ese elemento o al quitarlo respectivamente.
  • Formularios
    • ng-checked: Bool que indica el valor de un checkbox.
    • ng-selected: Bool que indica qué opción de un <select> está seleccionada.
    • ng-disabled: Bool que indica el estado de habilitación de un input.
    • ng-submit: El callback que se llamará cuando se envíe el formulario.

Para consultarlas todas la mejor opción es dirigirnos a la documentación oficial.

Nombrar e invocar una directiva

Lo primero que debemos definir a la hora de crear una directiva es su nombre. Hay que fijarse en que no colisione con ninguna otra directiva existente y seguir las normas de nomenclatura. Estas normas se basan en el estándar conocido como CamelCase. Consiste En unir todas las palabras sin espacios y capitalizar el primer carácter de cada una: comoPorEjemploEsto. El nombre del estándar hace referencia al perfil superior “jorobado” que resulta en la palabra.

Aunque la definición del nombre debe seguir esta norma, a la hora de usar la directiva tenemos diferentes formas de nombrarla para ser invocada. Por ejemplo, para una que hayamos definido como ngDirective podemos llamarla en el HTML como ng-directive, ng:directive, ng_directive, data-ng-directive, x-ng-directive indistintamente, teniendo todas ellas el mismo resultado. ¿Cual se recomienda? La única que pasa el validador al uso de HTML es data-ng-directive, dado que el prefijo data- es el que se reserva para atributos personalizados.

El siguiente aspecto que hay que tener en cuenta es en qué estructuras vamos a permitir que se invoque en el HTML. Una directiva puede ser llamada de cuatro formas diferentes:

  • Como elemento: <ng-directive>
  • Como atributo: <span ng-directive=”expr”>
  • Como clase: <span class=”ng-directive: expr”>
  • Como comentario: <!– directive: ng-directive exp –>

En este caso la más compacta es la primera, sin embargo Internet Explorer no soporta la inclusión de elementos personalizados, por tanto lo más recomendable si queremos dar soporte total es usar la segunda (con el prefijo data- por lo ya comentado).

Crear una directiva personalizada

En Angular las directivas van adjuntas a un módulo. Para crearlas, el objeto module expone el método directive. Una de las formas de adjuntarlas es la que se copia más abajo. Incluye un objeto de configuración con todos los atributos posibles (en este post analizaremos sólo los destacados):

Todas las variables de configuración son optativas dependiendo del propósito concreto que queremos alcanzar. Posteriormente veremos en detalle cómo se utilizan y para qué sirve cada una con ejemplos.

Configuración de directivas personalizadas en AngularJS

restrict

Indica en qué estructura puede invocarse la directiva. Como se apunta en capítulo “Nombrar e invocar una directiva” existen cuatro formas, cada una de ellas se especifica con uno de estos caracteres:

  • ‘A’: Como atributo: <span my-directive></span>
  • ‘E’: Como elemento: <my-directive></my-directive>
  • ‘C’: Como clase: <span class=”my-directive”></span>
  • ‘M’: Como comentario: <!– directive: my-directive –>

Siempre podemos combinar diferentes opciones concatenando los caracteres, como por ejemplo:

template / templateUrl

Para indicar qué HTML contendrá la etiqueta de la directiva una vez se renderice. Existen dos formas de especificarlo:

  • template: String con el literal del código HTML que queremos renderizar. Podemos colocar el string tal cual o una función que lo devuelva. Dicha función recibe referencias al elemento y a sus atributos como parámetros.
  • templateUrl: String con la URL del fichero donde se encuentra el HTML que nos interesa renderizar. Igualmente podemos pasar una URL constante o una función configurable mediante de la misma forma que en template.

Un ejemplo podría ser el siguiente:

scope

En general en AngularJS un scope es un entorno, un espacio de variables. Cada scope se asigna a un área del DOM (por ejemplo un <div>) y está disponible dentro de ella. Como el DOM de HTML presenta una estructura de árbol (con herencia entre padres e hijos) el scope de AngularJS también.

El atributo de configuración de directiva llamado scope es de los más importantes. Nos define de qué manera se relaciona el entorno interno de la directiva con su contexto (con el entorno de su padre). Y puede tomar tres diferentes valores:

  • false: Entonces la directiva utiliza directamente el scope del padre.
  • true: Se crea un nuevo scope para la directiva, pero que hereda del scope del padre.
  • {…} (Objeto): Se crea un nuevo scope independiente y casi estanco que no hereda más que de $rootScope. Sin embargo podemos conectarlo con el exterior mediante los atributos del elemento de la directiva. Para ello añadimos atributos al objeto de configuración de scope con alguno de los siguientes prefijos:
    • @: Recibe el contenido literal en string del atributo del elemento. Es capaz de evaluar el contenido de variables si se han introducido entre {{corchetes}}.
    • =: Recibe una variable del scope padre que se ha indicado en el atributo y la conecta de manera bidireccional con el scope de la directiva. El atributo indicado se convierte en obligatorio para la directiva, a nos ser que se indique el prefijo =?.
    • &: Similar al =, pero usado cuando la variable que se conecta es una función.

La forma de definirlo es la siguiente:

De cualquiera de las tres maneras, el scope que resulte estará disponible tanto en el template de la directiva, como en su controlador, como en las funciones compile y link que veremos a continuación.

compile / link

Estas funciones se utilizan para la inicializar la directiva y manipularla según la lógica que queramos definir. Las diferencias entre ambas son las siguientes:

  • compile: Se ejecuta una sola vez por directiva. Cuando se inicia la aplicación de AngularJS. Es lugar donde realizar funciones de inicialización que compartan todas las instancias futuras de esta directiva. Recibe como parámetros las referencias a todos los elementos (jQuery) que instancian la directiva en cuestión y sus atributos.
  • link: Se ejecuta una vez por cada instancia de la directiva. Es lugar donde activar los bindings y otras funciones concretas para según qué instancia y configuración.

Sus parámetros son el elemento (jQuery) en cuestión que está instanciando la directiva, sus parámetros y el scope de la directiva.

Además puede dividirse en dos métodos: pre y post. Estos se usan con el fin de controlar la ejecución en el caso de tener directivas anidadas, como veremos.

El objeto de configuración de la directiva nos da cierta flexibilidad para definir los métodos compile y link. Siendo posibles las siguientes opciones de más completa a más simple:

  • Método compile que devuelve un objeto con los métodos de link:

  • Método compile que devuelve el método post de link:

  • Únicamente el objeto link sus dos métodos:

  • Únicamente el método post de link:

El orden en que se se ejecutan las funciones características de una directiva (y sus posibles directivas hijas) es el siguiente:

Drectives flow AngularJS

Flujo de una directiva de AngularJS

 

require

Se utiliza para relacionar la directiva con otras directivas. Nombrándolas con el require estamos invocando una instancia de ellas, que recibiremos en forma de su controlador como parámetro extra en nuestra función link:

Hay varias formas de requerir otra directiva, y se configuran de nuevo con prefijos:

  • Sin prefijo: La directiva se requiere obligatoriamente en el mismo elemento donde se enuentra la que estamos definiendo nosotros.
  • ?: Se requiere de manera optativa en el mismo el elemento.
  • ^: Es requerido de manera obligatoria en algún elemento padre del nuestro en la jerarquía del DOM.
  • ?^: Se requiere en algún padre de manera optativa.

Además, podemos requerir más de una directiva colocando en el parámetro de configuración require un array de strings. De esta manera el parámetro del controlador de link será también un array con los elementos ordenados según se indicaron en el require.