referencia org docs component cli javascript angularjs

javascript - org - ng angular



$ postLink de un componente/directiva angular ejecutándose demasiado pronto (3)

Actualización: pongo una recompensa en esta pregunta. No estoy buscando hacks o soluciones alternativas. Estoy buscando una forma oficial de acceder al dom en un componente angular, y una explicación de por qué el comportamiento que veo ($ postLink hasta temprano) parece ser contradictorio con los documentos oficiales.
Los documentos oficiales declaran ( here ):

$ postLink (): se llama después de que el elemento de este controlador y sus hijos se hayan vinculado. De manera similar a la función de enlace posterior, este enganche se puede usar para configurar los controladores de eventos DOM y realizar la manipulación directa de DOM

Pregunta original: Tengo un ejemplo del problema aquí -> http://plnkr.co/edit/rMm9FOwImFRziNG4o0sg?p=preview

Estoy usando un componente angular y quiero modificar el dominio en la función de enlace posterior, pero no funciona, parece que la función se ejecuta demasiado pronto, antes de que la plantilla esté realmente lista en el dominio después de todo el procesamiento angular.

En la página html, tengo esto:

<my-grid grid-id="''foo''"></my-grid>

El componente se define como:

appModule.component(''myGrid'',{ controller: gridController, bindings: { "gridId": "<", }, templateUrl: ''gridTemplate'' });

En la plantilla de componentes tengo esto:

<table id=''{{$ctrl.gridId}}''> ...

(El enlace en sí funciona, no hay duda. Eventualmente, en el html el id de la tabla es ''foo'' como se esperaba).

En el controlador, tengo algo como esto:

function gridController($scope, $compile, $attrs) { console.log ("grid id is: " + this.gridId); // ''foo'' this.$postLink = function() { var elem = document.getElementById(this.gridId); // do something with elem, but elem is null } }

Lo que veo al depurar es que cuando se ejecuta la función $ postLink, la tabla está en el dom pero su atributo id sigue siendo {{$ctrl.gridId}} lugar de foo , por lo que document.getElementById() no encuentra nada. Esto parece en contraste con la documentación.
¿Qué me estoy perdiendo? ¿Hay alguna forma diferente de acceder al dom en el componente?

Actualización 2: Hoy me di cuenta de que el mismo problema ocurre con la función de enlace regular de las directivas, no se limita a los componentes. Aparentemente, malentendí el significado de "hacer manipulación directa del DOM": la función de enlace se ejecuta en un elemento que se separa del dominio, por lo que usar el objeto de document con selectores es inútil.


El malentendido está en cómo funciona el ciclo angular de digestión junto con los eventos del ciclo de vida del controlador (y los eventos del ciclo de vida de la directiva).

Cada uno de los eventos del ciclo de vida del controlador / directiva se llama dentro de $ apply. Debido a esto, el DOM solo se actualiza para reflejar los cambios después de que se complete el ciclo $ digest .

La función de enlace posterior se ejecuta después de que todo se haya vinculado, pero es al final de esta función que DOM se actualiza para reflejar cualquier enlace, debido a que es la última función ejecutada dentro del ciclo $ digest. Esto significa que tratar de consultar el DOM por cualquier cosa afectada por un enlace no funcionará (como una ng-repeat , o en su caso, un valor enlazado). Pero como el estado de los documentos, el enlace del DOM ya se ha ejecutado, por lo que la plantilla inicial se ha creado y se puede manipular.

Como necesita consultar el DOM después de que se hayan aplicado los enlaces, debe ejecutar su código una vez que se haya completado el ciclo actual de $ digest. Esto significa usar un $timeout con un retraso de 0, que se ejecutará justo después de que finalice el ciclo $ digest actual .

function gridController($scope, $compile, $attrs, $timeout) { console.log ("grid id is: " + this.gridId); // ''foo'' this.$postLink = function() { $timeout(function() { var elem = document.getElementById(this.gridId); // do something with elem now that the DOM has had it''s bindings applied }); } }


La documentación relativa a $postLink() es correcta. Se llama por el elemento de su controlador y sus hijos han sido vinculados. Esto no significa que verá el resultado de una directiva inmediatamente. Tal vez está llamando a $http e insertando el resultado una vez que llega. Tal vez esté registrando un observador que a su vez establece el resultado, como lo hacen la mayoría de las directivas integradas de Angular .

El problema subyacente en su caso es que desea realizar manipulaciones de DOM después de que se hayan compilado las interpolaciones , o mejor aún, después de que su observador registrado haya tenido tiempo de correr una vez.

Desafortunadamente, no hay una forma oficial de hacer esto. Sin embargo, hay formas de lograrlo, pero no las encontrará en la documentación.

Dos formas populares de ejecutar una función después de compilar las interpolaciones son:

  • usando $timeout sin demora (como su valor predeterminado es 0): $timeout(function() { /* Your code goes here */ });

  • usando .ready() que es proporcionado por jqLite de Angular

En su caso, es mucho mejor usar un observador para ejecutar una función una vez que exista el elemento con la ID dada:

var deregistrationFn = $scope.$watch(() => { return document.getElementById(this.gridId); }, (newValue) => { if (newValue !== null) { deregistrationFn(); // Your code goes here } });

Finalmente, en mi opinión, creo que siempre que necesite esperar a que se compilen las interpolaciones, o que ciertas directivas inserten su valor, no está siguiendo la manera en que Angular construye las cosas . En su caso, ¿por qué no crear un nuevo componente, myGridTable que requiere myGrid como padre, y agregar allí su lógica apropiada? De esta manera, la responsabilidad de cada componente está mucho mejor definida y es más fácil probar las cosas.


Parece relevante para comprender los escenarios de un componente angular antes de dar una solución. Y lo que veo en su ejemplo, la plantilla DOM cargada en un ámbito aislado . Repetir un alcance aislado.

Asi que,

De acuerdo con el documento oficial [1.5.6] sobre componentes here

Cuando no usar componentes:

  • para directivas que dependen de la manipulación de DOM, agregar detectores de eventos, etc., porque las funciones de compilación y enlace no están disponibles
  • cuando necesite opciones de definición de directivas avanzadas como prioridad, terminal, multi-elemento
  • cuando desee una directiva que se active mediante un atributo o una clase CSS, en lugar de un elemento

Si revisa la sección "Comparación entre la definición de directiva y la definición de componente" del documento, verá

  • característica ------------ directiva ----------- componente
  • enlaces ------------ No ------------------- Sí (se une al controlador)
  • bindToController ---- Sí (predeterminado: falso) -No (use enlaces en su lugar)
  • función de compilación ---- Sí ------------------ No
  • controlador ---------- Sí ------------------ Sí (función predeterminada () {})
  • controllerAs -------- Sí ------------------ (predeterminado: falso) Sí (predeterminado: $ ctrl)
  • funciones de enlace ------ Sí ------------------ No

Ahora

Los componentes nunca deben modificar ningún dato o DOM que esté fuera de su alcance . Normalmente, en Angular es posible modificar los datos en cualquier lugar de la aplicación a través de la herencia de alcance y los relojes. Esto es práctico, pero también puede ocasionar problemas cuando no está claro qué parte de la aplicación es responsable de modificar los datos. Es por eso que las directivas de componentes utilizan un ámbito aislado , por lo que no es posible una manipulación de toda clase de ámbitos. REF : ng-doc 1.5.6

Pero cuando lo ve, hay un enlace publicitario de $ postLink () disponible, puede que se pregunte cuál es la razón para ello.

Sin embargo, si pasas por la descripción de este gancho encontrarás ...

Tenga en cuenta que los elementos secundarios que contienen directivas templateUrl no se habrán compilado ni enlazado, ya que están esperando que su plantilla se cargue de forma asíncrona y su propia compilación y vinculación se hayan suspendido hasta que eso ocurra.

Por favor, consulte las respuestas de Steven Lambert a continuación ...