AngularJS + JQuery: cómo hacer que el contenido dinámico funcione en angularjs

84

Estoy trabajando en una aplicación Ajax usando jQuery y AngularJS.

Cuando actualizo el contenido (que contiene enlaces AngularJS) de un div usando la htmlfunción de jQuery , los enlaces AngularJS no funcionan.

A continuación se muestra el código de lo que estoy tratando de hacer:

$(document).ready(function() {
  $("#refreshButton").click(function() {
    $("#dynamicContent").html("<button ng-click='count = count + 1' ng-init='count=0'>Increment</button><span>count: {{count}} </span>")
  });
});
</style><script src="http://docs.angularjs.org/angular-1.0.1.min.js"></script><style>.ng-invalid {
  border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="">
  <div id='dynamicContent'>
    <button ng-click="count = count + 1" ng-init="count=0">
        Increment
      </button>
    <span>count: {{count}} </span>
  </div>


  <button id='refreshButton'>
    Refresh
  </button>
</div>

Tengo contenido dinámico dentro de un div con la ID #dynamicContent, y tengo un botón de actualización que actualizaría el contenido de este div cuando se hace clic en actualizar. Increment funciona como se esperaba si no actualizo el contenido, pero después de actualizar, el enlace AngularJS deja de funcionar.

Es posible que esto no sea válido en AngularJS, pero inicialmente creé la aplicación con jQuery y comencé a usar AngularJS más adelante, así que no puedo migrar todo a AngularJS. Cualquier ayuda para que esto funcione en AngularJS es muy apreciada.

Raja
fuente
1
¿Existe alguna razón en particular para usar JQuery para esta funcionalidad? Como esto se cubre de manera agradable y fácil con angular: < jsfiddle.net/pkozlowski_opensource/YCrFD/2 >
pkozlowski.opensource
3
Esta es solo una versión simplificada de mi caso de uso real para mostrar el problema. En la aplicación real, el contenido dinámico es generado por grails taglib que se pasa a jquery como html. Por lo tanto, no puedo transferir toda la lógica en grails taglib a angularjs para convertirla en pura angularjs.
Raja
1
@ pkozlowski.opensource, ¿ese enlace está muerto? También deberías unirte a outdoors.stackexchange.com :)
Liam

Respuestas:

115

Debe llamar $compilea la cadena HTML antes de insertarla en el DOM para que angular tenga la oportunidad de realizar el enlace.

En tu violín, se vería así.

$("#dynamicContent").html(
  $compile(
    "<button ng-click='count = count + 1' ng-init='count=0'>Increment</button><span>count: {{count}} </span>"
  )(scope)
);

Obviamente, $compiledebe inyectarse en su controlador para que esto funcione.

Leer más en la $compiledocumentación .

Noah Freitas
fuente
2
Gracias Noah, la compilación funciona cuando lo hago en un método de controlador y vinculo directamente ese método de controlador al evento de clic del botón. Aquí está un ejemplo de trabajo . Pero en mi aplicación real, tuve que llamar al método del controlador desde una función jquery diferente. así que tomé una referencia al objeto de alcance y llamé al método de actualización para volver a compilar, lo que no funciona. He aquí un ejemplo . ¿Puede ayudarnos a hacer que este ejemplo funcione?
Raja
1
@Raja Cuando llama a métodos / expresiones angulares fuera del marco angular, debe llamar a $ scope. $ Apply () para realizar el ciclo de vida del alcance adecuado de manejo de excepciones, ejecución de relojes, etc. jsfiddle.net/fABdD/14
Artem Andreev
1
@ArtemAndreev exactamente correcto. @Raja, también puede mover la llamada a $scope.$apply()la refresh()función en sí si tiene sentido para su arquitectura: jsfiddle.net/nfreitas/8xkeh/1
Noah Freitas
3
El enlace angular-1.0.1.min.jsen los ejemplos originales era 404. Aquí hay versiones de trabajo actualizadas del ejemplo de @ ArtemAndreev-s: jsfiddle.net/pmorch/fABdD/186 y ejemplo de @NoahFreitas
Peter V. Mørch
1
Usar $ compile y manipular DOM dentro de un controlador lo llevará por el camino de tener un código no comprobable. Deben usarse directivas para cambiar el DOM, nunca un controlador.
Enzey
57

Otra solución en caso de que no tenga control sobre el contenido dinámico

Esto funciona si no cargó su elemento a través de una directiva (es decir, como en el ejemplo de los jsfiddles comentados).

Resuma su contenido

Envuelva su contenido en un div para que pueda seleccionarlo si está usando JQuery . También puede optar por utilizar javascript nativo para obtener su elemento.

<div class="selector">
    <grid-filter columnname="LastNameFirstName" gridname="HomeGrid"></grid-filter>
</div>

Usar inyector angular

Puede usar el siguiente código para obtener una referencia a $ compile si no tiene una.

$(".selector").each(function () {
    var content = $(this);
    angular.element(document).injector().invoke(function($compile) {
        var scope = angular.element(content).scope();
        $compile(content)(scope);
    });
});

Resumen

La publicación original parecía asumir que tenías una referencia de compilación de $ a la mano. Obviamente, es fácil cuando tienes la referencia, pero yo no, así que esta fue la respuesta para mí.

Una advertencia del código anterior

Si está utilizando un paquete asp.net/mvc con escenario minify, tendrá problemas cuando implemente en modo de lanzamiento. El problema viene en forma de Error no detectado: [$ injector: unpr] que es causado por el minificador jugando con el código javascript angular.

Esta es la forma de remediarlo :

Reemplace el fragmento de código anterior con la siguiente sobrecarga.

...
angular.element(document).injector().invoke(
[
    "$compile", function($compile) {
        var scope = angular.element(content).scope();
        $compile(content)(scope);
    }
]);
...

Esto me causó mucho dolor antes de reconstruirlo.

jwize
fuente
1
Gracias porque el código anterior funcionó para mí, el alcance y la explicación sobre la compilación está disponible. a veces te pierdes las partes fáciles :)
Abs
Tuve que $ digerir después de la compilación.
Jue
2
Salvavidas absoluto, agregué una verificación condicional para angular en mi código que le permite usar toda la bondad angular cuando se conecta a una aplicación angular
LiamRyan
18

Adición a la respuesta de @ jwize

Porque angular.element(document).injector()estaba dando un error injector is not defined Entonces, he creado una función que puede ejecutar después de la llamada AJAX o cuando se cambia DOM usando jQuery.

  function compileAngularElement( elSelector) {

        var elSelector = (typeof elSelector == 'string') ? elSelector : null ;  
            // The new element to be added
        if (elSelector != null ) {
            var $div = $( elSelector );

                // The parent of the new element
                var $target = $("[ng-app]");

              angular.element($target).injector().invoke(['$compile', function ($compile) {
                        var $scope = angular.element($target).scope();
                        $compile($div)($scope);
                        // Finally, refresh the watch expressions in the new element
                        $scope.$apply();
                    }]);
            }

        }

utilícelo pasando solo el selector de nuevo elemento. Me gusta esto

compileAngularElement( '.user' ) ; 
shyammakwana.me
fuente
Gracias, esto me salvó la vida con un paquete que usaba UIKit para sus diálogos, con HTML generado sobre la marcha dentro de una función $ scope. Como nota al margen, tuve que cambiar el selector de $ target para satisfacer mis necesidades, en mi caso $ ["data-ng-controller]"), y comentar $ scope.apply (); dado que esta función ya se llamó en una devolución de llamada angular (por lo que la aplicación ya se estaba llevando a cabo)
zontar
@zontar también puede hacer targetdinámico tomándolo del argumento.
shyammakwana.me