¿hay una callback posterior para la directiva Angular JS?

Acabo de recibir mi directiva para obtener una plantilla para agregar a su elemento de esta manera:

# CoffeeScript .directive 'dashboardTable', -> controller: lineItemIndexCtrl templateUrl: "" (scope, element, attrs) -> element.parent('table#line_items').dataTable() console.log 'Just to make sure this is run' # HTML 

También estoy usando un complemento jQuery llamado DataTables. El uso general de esto es así: $ (‘table # some_id’). DataTable (). Puede pasar los datos JSON a la llamada dataTable () para suministrar los datos de la tabla O puede tener los datos ya en la página y hará el rest. Estoy haciendo lo último, teniendo las filas ya en la página HTML .

Pero el problema es que tengo que llamar a dataTable () en la tabla # line_items AFTER DOM ready. Mi directiva anterior llama al método dataTable () ANTES de que la plantilla se anexe al elemento de la directiva. ¿Hay alguna manera de llamar funciones DESPUÉS de agregar?

¡Gracias por tu ayuda!

ACTUALIZAR 1 después de la respuesta de Andy:

Quiero asegurarme de que el método de enlace solo se llame DESPUÉS de que todo esté en la página, así que modifiqué la directiva para una pequeña prueba:

 # CoffeeScript #angular.module(...) .directive 'dashboardTable', -> { link: (scope,element,attrs) -> console.log 'Just to make sure this gets run' element.find('#sayboo').html('boo') controller: lineItemIndexCtrl template: "
" }

Y realmente veo “boo” en el div # sayboo.

Luego pruebo mi llamada jquery datatable

 .directive 'dashboardTable', -> { link: (scope,element,attrs) -> console.log 'Just to make sure this gets run' element.parent('table').dataTable() # NEW LINE controller: lineItemIndexCtrl templateUrl: "" } 

No hay suerte allí

Luego trato de agregar un tiempo de espera:

 .directive 'dashboardTable', ($timeout) -> { link: (scope,element,attrs) -> console.log 'Just to make sure this gets run' $timeout -> # NEW LINE element.parent('table').dataTable() ,5000 controller: lineItemIndexCtrl templateUrl: "" } 

Y eso funciona Entonces me pregunto qué va mal en la versión del código que no es de temporizador.

Si no se proporciona el segundo parámetro, “retraso”, el comportamiento predeterminado es ejecutar la función después de que el DOM haya completado la representación. Entonces, en lugar de setTimeout, usa $ timeout:

 $timeout(function () { //DOM has finished rendering }); 

Tuve el mismo problema y creo que la respuesta es realmente no. Vea el comentario de Miško y alguna discusión en el grupo .

Angular puede rastrear que todas las llamadas a funciones que realiza para manipular el DOM están completas, pero dado que esas funciones podrían desencadenar una lógica asíncrona que todavía esté actualizando el DOM después de su retorno, no se podría esperar que Angular lo supiera. Cualquier callback angular podría funcionar a veces, pero no sería seguro confiar.

Solucionamos esto heurísticamente con un setTimeout, como lo hiciste tú.

(Tenga en cuenta que no todos están de acuerdo conmigo, debe leer los comentarios en los enlaces anteriores y ver lo que piensa).

Aunque mi respuesta no está relacionada con las tablas de datos, aborda el problema de la manipulación de DOM y, por ejemplo, la inicialización de jQuery plugin para directivas utilizadas en elementos que tienen sus contenidos actualizados de manera asíncrona.

En lugar de implementar un tiempo de espera uno podría simplemente agregar un reloj que escuchará los cambios de contenido (o incluso desencadenantes externos adicionales).

En mi caso utilicé esta solución alternativa para inicializar un plugin jQuery una vez que se realizó la repetición ng que creó mi DOM interno, en otro caso lo usé solo para manipular el DOM después de que la propiedad del scope se alteró en el controlador. Así es como lo hice …

HTML:

 
{{myContent}}

JS:

 app.directive('myDirective', [ function(){ return { restrict : 'A', scope : { myDirectiveWatch : '=' }, compile : function(){ return { post : function(scope, element, attributes){ scope.$watch('myDirectiveWatch', function(newVal, oldVal){ if (newVal !== oldVal) { // Do stuff ... } }); } } } } }]); 

Nota: En lugar de simplemente enviar la variable myContent a bool en el atributo my-directive-watch, uno podría imaginar cualquier expresión arbitraria allí.

Nota: Aislar el scope como en el ejemplo anterior solo se puede hacer una vez por elemento. Intentar hacer esto con varias directivas en el mismo elemento dará como resultado una comstackción $: error multidir – ver: https://docs.angularjs.org / error / $ compile / multidir

Puede usar la función ‘enlace’, también conocida como postLink, que se ejecuta después de colocar la plantilla.

 app.directive('myDirective', function() { return { link: function(scope, elm, attrs) { /*I run after template is put in */ }, template: 'Hello' } }); 

Dale una lectura si planeas hacer directivas, es una gran ayuda: http://docs.angularjs.org/guide/directive

Puede ser tarde para responder esta pregunta. Pero aún así, alguien puede obtener beneficio de mi respuesta.

Tuve un problema similar y en mi caso no puedo cambiar la directiva, ya que es una biblioteca y cambiar un código de la biblioteca no es una buena práctica. Entonces, lo que hice fue usar una variable para esperar la carga de la página y usar ng-si dentro de mi html para esperar renderizar el elemento en particular.

En mi controlador:

 $scope.render=false; //this will fire after load the the page angular.element(document).ready(function() { $scope.render=true; }); 

En mi html (en mi caso, el componente html es un canvas)

   

Tuve el mismo problema, pero usando Angular + DataTable con un fnDrawCallback + agrupación de filas + $ compiled nesteds directivas. Puse el $ timeout en mi función fnDrawCallback para corregir la representación de paginación.

Antes del ejemplo, basado en la fuente row_grouping:

 var myDrawCallback = function myDrawCallbackFn(oSettings){ var nTrs = $('table#result>tbody>tr'); for(var i=0; i 

Después del ejemplo:

 var myDrawCallback = function myDrawCallbackFn(oSettings){ var nTrs = $('table#result>tbody>tr'); $timeout(function requiredRenderTimeoutDelay(){ for(var i=0; i 

Incluso un breve tiempo de espera fue suficiente para permitir a Angular presentar mis directivas angulares comstackdas.

Ninguna de las soluciones funcionó para mí aceptar el uso de un tiempo de espera. Esto se debe a que estaba usando una plantilla que se creaba dinámicamente durante el postLink.

Sin embargo, tenga en cuenta que puede haber un tiempo de espera de ‘0’ ya que el tiempo de espera agrega la función que se llama a la cola del navegador que ocurrirá después del motor de representación angular ya que este ya está en la cola.

Consulte esto: http://blog.brunoscopelliti.com/run-a-directive-after-the-dom-has-finished-rendering

Aquí hay una directiva para tener acciones progtwigdas después de una renderización superficial. Por superficial quiero decir que evaluará después de que se haya prestado ese mismo elemento y que no estará relacionado con cuándo se procesa su contenido. Por lo tanto, si necesita algún sub elemento haciendo una acción de renderización posterior, debería considerar usarlo allí:

 define(['angular'], function (angular) { 'use strict'; return angular.module('app.common.after-render', []) .directive('afterRender', [ '$timeout', function($timeout) { var def = { restrict : 'A', terminal : true, transclude : false, link : function(scope, element, attrs) { if (attrs) { scope.$eval(attrs.afterRender) } scope.$emit('onAfterRender') } }; return def; }]); }); 

entonces puedes hacer:

o con cualquier expresión útil como:

Lo hice trabajando con la siguiente directiva:

 app.directive('datatableSetup', function () { return { link: function (scope, elm, attrs) { elm.dataTable(); } } }); 

Y en el HTML:

 

solución de problemas si lo anterior no funciona para usted.

1) tenga en cuenta que ‘datatableSetup’ es el equivalente de ‘datatable-setup’. Angular cambia el formato en camel case.

2) asegúrese de que la aplicación esté definida antes de la directiva. por ejemplo, definición y directiva de aplicación simple.

 var app = angular.module('app', []); app.directive('datatableSetup', function () { return { link: function (scope, elm, attrs) { elm.dataTable(); } } }); 

Siguiendo el hecho de que no se puede anticipar el orden de carga, se puede usar una solución simple.

Veamos la relación directiva-‘usuario de la directiva ‘. Por lo general, el usuario de la directiva proporcionará algunos datos a la directiva o utilizará algunas funcionalidades (funciones) que la directiva suministra. La directiva, por otro lado, espera que se definan algunas variables en su scope.

Si podemos asegurarnos de que todos los jugadores cumplan con todos sus requisitos de acción antes de intentar ejecutar esas acciones, todo debería estar bien.

Y ahora la directiva:

 app.directive('aDirective', function () { return { scope: { input: '=', control: '=' }, link: function (scope, element) { function functionThatNeedsInput(){ //use scope.input here } if ( scope.input){ //We already have input functionThatNeedsInput(); } else { scope.control.init = functionThatNeedsInput; } } }; }) 

y ahora el usuario de la directiva html

  

y en algún lugar del controlador del componente que usa la directiva:

 $scope.control = {}; ... $scope.input = 'some data could be async'; if ( $scope.control.functionThatNeedsInput){ $scope.control.functionThatNeedsInput(); } 

Eso es todo. Hay una gran cantidad de gastos generales, pero puede perder $ timeout. También suponemos que el componente que usa la directiva se instancia antes de la directiva porque dependemos de la variable de control que existe cuando se crea una instancia de la directiva.