Directivas angulares: cuándo y cómo usar compilación, controlador, preenlace y postenlace [cerrado]

451

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

  • compilar
  • controlador
  • preenlace
  • post-enlace

Parece haber cierta confusión acerca de qué función se debe usar. Esta pregunta cubre:

Directiva básica

Función naturaleza, qué hacer y qué no hacer

Preguntas relacionadas:

Izhaki
fuente
27
Que el que ?
Haimlit
2
@ Ian See: sobrecarga del operador . Básicamente, esto está destinado a la comunidad wiki. Demasiadas de las respuestas a las preguntas relacionadas son parciales, no proporcionan la imagen completa.
Izhaki
8
Este es un gran contenido, pero pedimos que todo aquí se mantenga dentro del formato de preguntas y respuestas. ¿Quizás le gustaría dividir esto en múltiples preguntas discretas y luego vincularlas desde la etiqueta wiki?
Flexo
57
Aunque esta publicación está fuera de tema y en forma de blog, fue muy útil para proporcionar una explicación detallada de las directivas angulares. ¡No eliminen esta publicación, administradores!
Exégesis del
12
Honestamente, ni siquiera me molesto con los documentos originales. Una publicación de stackoverflow o un blog generalmente me ayuda en cuestión de segundos, en comparación con los 15-30 minutos de desgarrarme el pelo tratando de entender los documentos originales.
David

Respuestas:

168

¿En qué orden se ejecutan las funciones directivas?

Para una sola directiva

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

<body>
    <div log='some-div'></div>
</body>

Con la siguiente declaración 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 compilese ejecuta primero, luego controller, luegopre-link y el último espost-link .

Para directivas anidadas

Nota: Lo siguiente no se aplica a las directivas que muestran a sus hijos en su función de enlace. Algunas directivas angulares lo hacen (como ngIf, ngRepeat o cualquier directiva con transclude). Estas directivas tendrán su linkfunción de forma nativa antes de que se llamen sus directivas secundarias compile.

El marcado HTML original a menudo está hecho de elementos anidados, cada uno con su propia directiva. Como en el siguiente marcado (ver plunk ):

<body>
    <div log='parent'>
        <div log='..first-child'></div>
        <div log='..second-child'></div>
    </div>
</body>

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 compilación y el enlace. fase de .

La fase de compilación

Cuando se carga el DOM, Angular comienza la fase de compilación, donde atraviesa el marcado de arriba hacia abajo y llama compilea todas las directivas. Gráficamente, podríamos expresarlo así:

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

Quizás sea importante mencionar que en esta etapa, las plantillas que obtiene la función de compilación son las plantillas de origen (no la plantilla de instancia).

La fase de enlace

Las instancias DOM a menudo son simplemente el resultado de una plantilla fuente que se representa 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 controller, pre-linkitera hijos y llama post-linka todas las directivas, así:

Una ilustración que muestra los pasos de la fase de enlace

Izhaki
fuente
55
@lzhaki El diagrama de flujo se ve bien. ¿Te importaría compartir el nombre de la herramienta de gráficos? :)
merlin
1
@merlin He usado OmniGraffle (pero podría haber usado Illustrator o Inkscape, aparte de la velocidad, no hay nada que OmniGraffle haga mejor que otras herramientas de gráficos en lo que respecta a esta ilustración).
Izhaki
2
El plunker de @ Anant desapareció, así que aquí hay uno nuevo: plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview Abra la consola JS para ver las declaraciones de registro
¿POR QUÉ esto no es cierto cuando ng-repeat se usa para directivas de niños? Ver plunk: plnkr.co/edit/HcH4r6GV5jAFC3yOZknc?p=preview
Luckylooke
@Luckylooke Su plunk no tiene hijos con directiva bajo ng-repeat (es decir, lo que se repite es una plantilla con una directiva. Si lo hiciera, vería que su compilación solo se llama después del enlace de ng-repeat.
Izhaki
90

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

Las diversas funciones directivas se ejecutan desde dentro de otras dos funciones angulares llamados $compile(donde la directiva de compilese ejecuta) y una función interna llamada nodeLinkFn(donde el de directiva controller, preLinky postLinkse ejecutan). Suceden varias cosas dentro de la función angular antes y después de que se invocan las funciones directivas. Quizás lo más notable es la recursividad infantil. La siguiente ilustración simplificada muestra los pasos clave dentro de las fases de compilación y enlace:

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

Para demostrar estos pasos, usemos el siguiente marcado HTML:

<div ng-repeat="i in [0,1,2]">
    <my-element>
        <div>Inner content</div>
    </my-element>
</div>

Con la siguiente directiva:

myApp.directive( 'myElement', function() {
    return {
        restrict:   'EA',
        transclude: true,
        template:   '<div>{{label}}<div ng-transclude></div></div>'
    }
});

Compilar

La compileAPI se ve así:

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

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

Antes de la llamada al compilecontenido transcluido (si lo hay) se elimina, y la plantilla se aplica al marcado. Por lo tanto, el elemento proporcionado a la compilefunción se verá así:

<my-element>
    <div>
        "{{label}}"
        <div ng-transclude></div>
    </div>
</my-element>

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

Siguiendo la llamada a las directivas .compile , Angular atravesará todos los elementos secundarios, incluidos los que pueden haber sido introducidos por la directiva (los elementos de plantilla, por ejemplo).

Creación de instancia

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

Controlador

La controllerAPI implica:

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

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

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

Luego se ejecuta el controlador, provisto con el alcance del elemento de instancia.

Preenlace

La pre-linkAPI se ve así:

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

Prácticamente no sucede nada entre la llamada a las directivas .controllery la .preLinkfunción. Angular todavía proporciona recomendaciones sobre cómo se debe usar cada una.

Después de la .preLinkllamada, la función de enlace atravesará cada elemento secundario: llamará a la función de enlace correcta y le asignará el alcance actual (que sirve como el alcance principal para los elementos secundarios).

Post-enlace

La post-linkAPI es similar a la de la pre-linkfunción:

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

Quizás valga la pena notar que una vez que .postLinkse llama a la función de una directiva , el proceso de enlace de todos sus elementos hijos se ha completado, incluidas todas las .postLinkfunciones infantiles .

Esto significa que cuando .postLinkse llama, los niños están "vivos" y listos. Esto incluye:

  • el enlace de datos
  • transclusión aplicada
  • alcance adjunto

La plantilla en esta etapa se verá así:

<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>
Izhaki
fuente
3
¿Cómo creaste este dibujo?
Royi Namir el
66
@RoyiNamir Omnigraffle.
Izhaki el
43

¿Cómo declarar las diversas funciones?

Compilar, Controlador, Preenlace y Postenlace

Si se van a utilizar las cuatro funciones, la directiva seguirá este formulario:

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
                }
            };
        }
    };  
});

Tenga en cuenta que compilar devuelve un objeto que contiene las funciones de preenlace y postenlace; En la jerga angular, decimos que la función de compilación devuelve una función de plantilla .

Compilación, controlador y post-enlace

Si pre-linkno es necesario, la función de compilación simplemente puede devolver la función de enlace posterior en lugar de un objeto de definición, de esta 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 compilemétodo, después de que linkse definió el método (post) . 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-enlace

Si no se necesita una función de compilación, se puede omitir su declaración por completo y proporcionar la función de enlace posterior bajo la linkpropiedad 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 controllerfunción si no es necesario. Entonces, por ejemplo, si solo post-linkse necesita una función, uno puede usar:

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

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

El hecho de que Angular permita la manipulación DOM significa que el marcado de entrada en el proceso de compilación a veces difiere de la salida. En particular, algunas marcas de entrada pueden clonarse varias veces (como con ng-repeat) antes de representarse en el 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 va a clonar, si es necesario. Si se clona, ​​este marcado no se representará en el DOM.
  • Plantilla de instancia : el marcado real que se representará en el DOM. Si la clonación está involucrada, cada instancia será un clon.

El siguiente marcado demuestra esto:

<div ng-repeat="i in [0,1,2]">
    <my-directive>{{i}}</my-directive>
</div>

La fuente html define

    <my-directive>{{i}}</my-directive>

que sirve como plantilla de origen.

Pero como está envuelto dentro de una ng-repeatdirectiva, esta plantilla fuente será clonada (3 veces en nuestro caso). Estos clones son una plantilla de instancia, cada uno aparecerá en el DOM y estará vinculado al ámbito relevante.

Izhaki
fuente
23

Función de compilación

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

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

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

<tr ng-repeat="raw in raws">
    <my-raw></my-raw>
</tr>

La <my-raw>directiva representará un conjunto particular de marcado DOM. Entonces podemos:

  • Permita ng-repeatduplicar la plantilla de origen ( <my-raw>) y luego modifique el marcado de cada plantilla de instancia (fuera de la compilefunción).
  • Modifique la plantilla de origen para incluir el marcado deseado (en la compilefunción) y luego permita ng-repeatduplicarlo.

Si hay 1000 elementos en la rawscolección, 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

  • Adjunte controladores de eventos.
  • Inspeccionar elementos secundarios.
  • Establecer observaciones sobre los atributos.
  • Configurar relojes en el alcance.
Izhaki
fuente
20

Función de controlador

La controllerfunción de cada directiva se llama cada vez que se crea una instancia de un nuevo elemento relacionado.

Oficialmente, la controllerfunción es donde uno:

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

Nuevamente, es importante recordar que si la directiva involucra un alcance aislado, las propiedades dentro de ella que heredan del alcance padre aún no están disponibles.

Hacer:

  • Definir la lógica del controlador
  • Iniciar variables de alcance

No haga:

  • Inspeccione los elementos secundarios (es posible que todavía no estén representados, vinculados al alcance, etc.).
Izhaki
fuente
Me alegra haber mencionado que Controller dentro de la directiva es un gran lugar para inicializar el alcance. Me costó mucho descubrir eso.
jsbisht
1
El controlador NO "inicia el alcance", solo accede al alcance ya iniciado independientemente de él.
Dmitri Zaitsev
@DmitriZaitsev buena atención a los detalles. He modificado el texto.
Izhaki
19

Función de post-enlace

Cuando post-linkse llama a la función, se han realizado todos los pasos anteriores: enlace, transclusión, etc.

Este suele ser un lugar para manipular aún más el DOM representado.

Hacer:

  • Manipular elementos DOM (renderizados y, por lo tanto, instanciados).
  • Adjunte controladores de eventos.
  • Inspeccionar elementos secundarios.
  • Establecer observaciones sobre los atributos.
  • Configurar relojes en el alcance.
Izhaki
fuente
99
En caso de que alguien esté usando la función de enlace (sin pre-enlace o post-enlace), es bueno saber que es equivalente al post-enlace.
Asaf David
15

Función de preenlace

La pre-linkfunción de cada directiva se llama cada vez que se crea una instancia de un nuevo elemento relacionado.

Como se vio anteriormente en la sección de orden de compilación, las pre-linkfunciones se llaman padre-luego-hijo, mientras que las post-linkfunciones se llaman child-then-parent.

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

No haga:

  • Inspeccione los elementos secundarios (es posible que todavía no estén representados, vinculados al alcance, etc.).
Izhaki
fuente