Directivas angulares: cuándo y cómo usar comstackr, controlar, pre-link y post-link

Al escribir una directiva angular, uno puede usar cualquiera de las siguientes funciones para manipular el comportamiento, el contenido y el aspecto del DOM del elemento en el que se declara la directiva:

  • comstackr
  • controlador
  • pre-link
  • post-link

Parece haber cierta confusión en cuanto a qué función debería uno usar. Esta pregunta cubre:

Principios básicos de la directiva

  • ¿Cómo declarar las diversas funciones?
  • ¿Cuál es la diferencia entre una plantilla de origen y una plantilla de instancia ?
  • ¿En qué orden se ejecutan las funciones directivas?
  • ¿Qué más sucede entre estas llamadas a funciones?

Función naturaleza, hacer y no hacer

  • Comstackr
  • Controlador
  • Prevínculo
  • Post-link

Preguntas relacionadas:

  • Directiva: enlace vs comstackción vs controlador .
  • Diferencia entre las funciones ‘controlador’, ‘enlace’ y ‘comstackción’ cuando se define una directiva angular.js .
  • Cuál es la diferencia entre la función de comstackción y enlace en angularjs .
  • ¿Diferencia entre el elemento de precomstackción y post comstackción en las directivas AngularJS? .
  • Directiva Angular JS – ¿Plantilla, comstackción o enlace? .
  • enlace posterior vs enlace previo en directivas js angulares .

¿En qué orden se ejecutan las funciones directivas?

Para una sola directiva

Basado en el siguiente plunk , considere el siguiente marcado HTML:

 

Con la siguiente statement de directiva:

 myApp.directive('log', function() { return { controller: function( $scope, $element, $attrs, $transclude ) { console.log( $attrs.log + ' (controller)' ); }, compile: function compile( tElement, tAttributes ) { console.log( tAttributes.log + ' (compile)' ); return { pre: function preLink( scope, element, attributes ) { console.log( attributes.log + ' (pre-link)' ); }, post: function postLink( scope, element, attributes ) { console.log( attributes.log + ' (post-link)' ); } }; } }; }); 

La salida de la consola será:

 some-div (compile) some-div (controller) some-div (pre-link) some-div (post-link) 

Podemos ver que la compile se ejecuta primero, luego el controller , luego pre-link y el último es post-link .

Para directivas anidadas

Nota: Lo siguiente no se aplica a las directivas que representan a sus hijos en su función de enlace. Bastantes directivas angulares lo hacen (como ngIf, ngRepeat o cualquier directiva con transclude ). A estas directivas se les llamará de manera nativa a su función de link antes de que se convoque su compile directivas secundarias.

El marcado HTML original a menudo está compuesto de elementos nesteds, cada uno con su propia directiva. Al igual que en el siguiente marcado (ver plunk ):

  

La salida de la consola se verá así:

 // The compile phase parent (compile) ..first-child (compile) ..second-child (compile) // The link phase parent (controller) parent (pre-link) ..first-child (controller) ..first-child (pre-link) ..first-child (post-link) ..second-child (controller) ..second-child (pre-link) ..second-child (post-link) parent (post-link) 

Aquí podemos distinguir dos fases: la fase de comstackción y la fase de enlace .

La fase de comstackción

Cuando se carga DOM, Angular inicia la fase de comstackción, donde atraviesa el marcado de arriba hacia abajo y las llamadas se compile en todas las directivas. Gráficamente, podríamos expresslo así:

Una imagen que ilustra el ciclo de compilación para niños

Es quizás importante mencionar que en esta etapa, las plantillas que obtiene la función de comstackción son las plantillas fuente (no la plantilla de instancia).

La fase de enlace

Las instancias DOM a menudo son simplemente el resultado de una plantilla fuente que se procesa en el DOM, pero pueden ser creadas por ng-repeat o introducidas sobre la marcha.

Cada vez que se representa una nueva instancia de un elemento con una directiva en el DOM, comienza la fase de enlace.

En esta fase, Angular llama al controller , pre-link , itera a los niños, y llama a post-link en todas las directivas, así:

Una ilustración que demuestra los pasos de fase del enlace

¿Qué más sucede entre estas llamadas a funciones?

Las diversas funciones directivas se ejecutan desde otras dos funciones angulares llamadas $compile (donde se ejecuta la compile la directiva) y una función interna llamada nodeLinkFn (donde se ejecutan el controller la directiva, preLink y postLink ). Varias cosas suceden dentro de la función angular antes y después de que se llamen las funciones directivas. Quizás lo más notable es la recursión infantil. La siguiente ilustración simplificada muestra los pasos clave dentro de las fases de comstackción y enlace:

Una ilustración que muestra las fases de compilación y enlace angular

Para demostrar estos pasos, usemos el siguiente marcado HTML:

 
Inner content

Con la siguiente directiva:

 myApp.directive( 'myElement', function() { return { restrict: 'EA', transclude: true, template: '
{{label}}
' } });

Comstackr

La API de compile ve así:

 compile: function compile( tElement, tAttributes ) { ... } 

A menudo, los parámetros tienen el prefijo t para significar que los elementos y atributos proporcionados son los de la plantilla fuente, en lugar de la instancia.

Antes de que se elimine la llamada para compile contenido transcluido (si hay alguno), y la plantilla se aplica al marcado. Por lo tanto, el elemento proporcionado a la función de compile se verá así:

  
"{{label}}"

Observe que el contenido transcluido no se vuelve a insertar en este punto.

Tras la llamada al .compile la directiva de la directiva, Angular atravesará todos los elementos secundarios, incluidos los que acaban de ser introducidos por la directiva (los elementos de la plantilla, por ejemplo).

Creación de instancia

En nuestro caso, se crearán tres instancias de la plantilla fuente de arriba (mediante ng-repeat ). Por lo tanto, la siguiente secuencia se ejecutará tres veces, una por instancia.

Controlador

El controller API implica:

 controller: function( $scope, $element, $attrs, $transclude ) { ... } 

Al ingresar a la fase de enlace, la función de enlace devuelta a través de $compile ahora se proporciona con un scope.

Primero, la función de enlace crea un ámbito hijo ( scope: true ) o un scope aislado ( scope: {...} ) si así lo solicita.

El controlador se ejecuta luego, provisto con el scope del elemento de instancia.

Prevínculo

La API pre-link ve así:

 function preLink( scope, element, attributes, controller ) { ... } 

Prácticamente no ocurre nada entre la llamada al .preLink . De la directiva y la función .preLink . Angular aún proporciona recomendaciones sobre cómo se debe usar cada uno.

Después de la llamada .preLink , la función de enlace recorrerá cada elemento secundario, invocando la función de enlace correcta y adjuntando el scope actual (que sirve como el scope principal para los elementos secundarios).

Post-link

La API post-link es similar a la de la función de pre-link :

 function postLink( scope, element, attributes, controller ) { ... } 

Quizás valga la pena observar que una vez que se llama a la función .postLink una directiva, se .postLink el proceso de enlace de todos sus elementos .postLink , incluidas todas las funciones .postLink los niños.

Esto significa que cuando se llama a .postLink , los niños están ‘en vivo’ listos. Esto incluye:

  • el enlace de datos
  • transclusion aplicada
  • scope adjunto

La plantilla en esta etapa se verá así:

  
"{{label}}"
Inner content

¿Cómo declarar las diversas funciones?

Comstackr, Controlar, Previncular y Post-link

Si uno va a usar las cuatro funciones, la directiva seguirá esta forma:

 myApp.directive( 'myDirective', function () { return { restrict: 'EA', controller: function( $scope, $element, $attrs, $transclude ) { // Controller code goes here. }, compile: function compile( tElement, tAttributes, transcludeFn ) { // Compile code goes here. return { pre: function preLink( scope, element, attributes, controller, transcludeFn ) { // Pre-link code goes here }, post: function postLink( scope, element, attributes, controller, transcludeFn ) { // Post-link code goes here } }; } }; }); 

Observe que compile devuelve un objeto que contiene las funciones pre-link y post-link; en la jerga angular decimos que la función de comstackción devuelve una función de plantilla .

Comstackr, Controlar y Post-link

Si pre-link no es necesario, la función de comstackción simplemente puede devolver la función de enlace posterior en lugar de un objeto de definición, de la siguiente manera:

 myApp.directive( 'myDirective', function () { return { restrict: 'EA', controller: function( $scope, $element, $attrs, $transclude ) { // Controller code goes here. }, compile: function compile( tElement, tAttributes, transcludeFn ) { // Compile code goes here. return function postLink( scope, element, attributes, controller, transcludeFn ) { // Post-link code goes here }; } }; }); 

A veces, uno desea agregar un método de compile , después de que se definió el método (post) de link . Para esto, uno puede usar:

 myApp.directive( 'myDirective', function () { return { restrict: 'EA', controller: function( $scope, $element, $attrs, $transclude ) { // Controller code goes here. }, compile: function compile( tElement, tAttributes, transcludeFn ) { // Compile code goes here. return this.link; }, link: function( scope, element, attributes, controller, transcludeFn ) { // Post-link code goes here } }; }); 

Controlador y Post-link

Si no se necesita una función de comstackción, se puede saltear su statement por completo y proporcionar la función de enlace posterior bajo la propiedad de link del objeto de configuración de la directiva:

 myApp.directive( 'myDirective', function () { return { restrict: 'EA', controller: function( $scope, $element, $attrs, $transclude ) { // Controller code goes here. }, link: function postLink( scope, element, attributes, controller, transcludeFn ) { // Post-link code goes here }, }; }); 

Sin controlador

En cualquiera de los ejemplos anteriores, uno simplemente puede eliminar la función del controller si no es necesario. Entonces, por ejemplo, si solo se necesita la función post-link , se puede usar:

 myApp.directive( 'myDirective', function () { return { restrict: 'EA', link: function postLink( scope, element, attributes, controller, transcludeFn ) { // Post-link code goes here }, }; }); 

¿Cuál es la diferencia entre una plantilla de origen y una plantilla de instancia ?

El hecho de que Angular permite la manipulación DOM significa que la marca de entrada en el proceso de comstackción a veces difiere de la salida. Particularmente, algunas marcas de entrada se pueden clonar varias veces (como con ng-repeat ) antes de renderizarlas al DOM.

La terminología angular es un poco inconsistente, pero aún distingue entre dos tipos de marcas:

  • Plantilla de origen : el marcado que se clonará, si es necesario. Si se clona, ​​este marcado no se representará en el DOM.
  • Plantilla de instancia : el marcado real que se procesará en el DOM. Si está involucrada la clonación, cada instancia será un clon.

El siguiente marcado demuestra esto:

 
{{i}}

La fuente html define

  {{i}} 

que sirve como la plantilla fuente.

Pero como está incluido dentro de una directiva ng-repeat , esta plantilla fuente será clonada (3 veces en nuestro caso). Estos clones son una plantilla de instancia, cada uno aparecerá en el DOM y se vinculará al scope relevante.

Compile la función

La función de compile cada directiva solo se llama una vez, cuando bootstraps angulares.

Oficialmente, este es el lugar para realizar manipulaciones de plantilla (fuente) que no implican scope o enlace de datos.

En primer lugar, esto se hace con fines de optimización; considere el siguiente marcado:

    

La directiva generará un conjunto particular de marcado DOM. Entonces podemos hacer lo siguiente:

  • Permita ng-repeat para duplicar la plantilla de origen ( ), y luego modifique el marcado de cada plantilla de instancia (fuera de la función de compile ).
  • Modifique la plantilla de origen para involucrar el marcado deseado (en la función de compile ), y luego permita que ng-repeat duplique.

Si hay 1000 artículos en la colección de raws , la última opción puede ser más rápida que la anterior.

Hacer:

  • Manipule el marcado para que sirva como plantilla para las instancias (clones).

No haga

  • Adjuntar controladores de eventos.
  • Inspeccione los elementos secundarios.
  • Configure las observaciones de los atributos.
  • Configurar relojes en el scope.

Función post-link

Cuando se llama a la función de post-link , se han llevado a cabo todos los pasos previos: enlace, transclusión, etc.

Este es típicamente un lugar para manipular aún más el DOM renderizado.

Hacer:

  • Manipular elementos DOM (renderizados, y por lo tanto instanciados).
  • Adjuntar controladores de eventos.
  • Inspeccione los elementos secundarios.
  • Configure las observaciones de los atributos.
  • Configurar relojes en el scope.

Función del controlador

Se llama a la función de controller cada directiva siempre que se crea una instancia de un nuevo elemento relacionado.

Oficialmente, la función del controller es donde uno:

  • Define la lógica del controlador (métodos) que pueden compartirse entre los controladores.
  • Inicia variables de scope

Nuevamente, es importante recordar que si la directiva involucra un scope aislado, las propiedades que heredan del scope principal aún no están disponibles.

Hacer:

  • Definir la lógica del controlador
  • Iniciar variables de ámbito

No haga:

  • Inspeccione los elementos secundarios (es posible que aún no se hayan renderizado, que estén vinculados al scope, etc.).

Función de enlace previo

Se llama a cada función de pre-link cada directiva cada vez que se crea una instancia de un nuevo elemento relacionado.

Como se vio anteriormente en la sección de orden de comstackción, pre-link funciones de pre-link se denominan padre-y-hijo, mientras que post-link funciones de post-link se denominan child-then-parent .

La función de pre-link rara vez se usa, pero puede ser útil en escenarios especiales; por ejemplo, cuando un controlador hijo se registra con el controlador principal, pero el registro tiene que ser parent-then-child ( ngModelController hace las cosas de esta manera).

No haga:

  • Inspeccione los elementos secundarios (es posible que aún no se hayan renderizado, que estén vinculados al scope, etc.).