Compilar cadenas HTML dinámicas de la base de datos

132

La situación

Anidado dentro de nuestra aplicación Angular hay una directiva llamada Page, respaldada por un controlador, que contiene un div con un atributo ng-bind-html-inseguro. Esto se asigna a un $ scope var llamado 'pageContent'. A esta var se le asigna HTML generado dinámicamente desde una base de datos. Cuando el usuario pasa a la página siguiente, se realiza una llamada a la base de datos y la variable pageContent se establece en este nuevo HTML, que se muestra en pantalla a través de ng-bind-html-inseguro. Aquí está el código:

Directiva de la página

angular.module('myApp.directives')
    .directive('myPage', function ($compile) {

        return {
            templateUrl: 'page.html',
            restrict: 'E',
            compile: function compile(element, attrs, transclude) {
                // does nothing currently
                return {
                    pre: function preLink(scope, element, attrs, controller) {
                        // does nothing currently
                    },
                    post: function postLink(scope, element, attrs, controller) {
                        // does nothing currently
                    }
                }
            }
        };
    });

Plantilla de directiva de página ("page.html" de la propiedad templateUrl anterior)

<div ng-controller="PageCtrl" >
   ...
   <!-- dynamic page content written into the div below -->
   <div ng-bind-html-unsafe="pageContent" >
   ...
</div>

Controlador de página

angular.module('myApp')
  .controller('PageCtrl', function ($scope) {

        $scope.pageContent = '';

        $scope.$on( "receivedPageContent", function(event, args) {
            console.log( 'new page content received after DB call' );
            $scope.pageContent = args.htmlStrFromDB;
        });

});

Eso funciona. Vemos el HTML de la página de la base de datos bien representada en el navegador. Cuando el usuario pasa a la página siguiente, vemos el contenido de la página siguiente, y así sucesivamente. Hasta aquí todo bien.

El problema

El problema aquí es que queremos tener contenido interactivo dentro del contenido de una página. Por ejemplo, el HTML puede contener una imagen en miniatura donde, cuando el usuario hace clic en él, Angular debería hacer algo increíble, como mostrar una ventana modal emergente. He colocado llamadas a métodos angulares (ng-click) en las cadenas HTML de nuestra base de datos, pero, por supuesto, Angular no reconocerá las llamadas a métodos ni las directivas a menos que de alguna manera analice la cadena HTML, las reconozca y las compile.

En nuestro DB

Contenido para la página 1:

<p>Here's a cool pic of a lion. <img src="lion.png" ng-click="doSomethingAwesone('lion', 'showImage')" > Click on him to see a large image.</p>

Contenido para la página 2:

<p>Here's a snake. <img src="snake.png" ng-click="doSomethingAwesone('snake', 'playSound')" >Click to make him hiss.</p>

De vuelta en el controlador de página, agregamos la función $ scope correspondiente:

Controlador de página

$scope.doSomethingAwesome = function( id, action ) {
    console.log( "Going to do " + action + " with "+ id );
}

No puedo entender cómo llamar a ese método 'doSomethingAwesome' desde la cadena HTML de la base de datos. Me doy cuenta de que Angular tiene que analizar la cadena HTML de alguna manera, pero ¿cómo? Leí murmullos vagos sobre el servicio de compilación $, y copié y pegué algunos ejemplos, pero nada funciona. Además, la mayoría de los ejemplos muestran que el contenido dinámico solo se configura durante la fase de enlace de la directiva. Queremos que Page se mantenga con vida durante toda la vida de la aplicación. Constantemente recibe, compila y muestra contenido nuevo a medida que el usuario pasa las páginas.

En un sentido abstracto, supongo que se podría decir que estamos tratando de anidar dinámicamente trozos de Angular dentro de una aplicación Angular, y necesitamos poder intercambiarlos dentro y fuera.

He leído varias partes de la documentación angular varias veces, así como todo tipo de publicaciones de blog, y JS jugueteó con el código de las personas. No sé si estoy entendiendo completamente mal Angular, o simplemente me falta algo simple, o tal vez soy lento. En cualquier caso, podría usar algunos consejos.

sentido de la jirafa
fuente
2
$ compile y los blogs de documentos que lo rodean me hacen sentir que también soy lento, a pesar de que siento que mi js es bastante fuerte, creo que si me familiarizo con esto, haré un blog de estilo idiotas, ¡esa es mi especialidad!
aterrizó el

Respuestas:

248

ng-bind-html-unsafesolo representa el contenido como HTML. No une el alcance angular al DOM resultante. Tienes que usar el $compileservicio para ese propósito. He creado este plunker para demostrar cómo utilizar $compilepara crear una representación HTML dinámico Directiva ponen los usuarios y la unión a alcance del controlador. La fuente se publica a continuación.

demo.html

<!DOCTYPE html>
<html ng-app="app">

  <head>
    <script data-require="[email protected]" data-semver="1.0.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js"></script>
    <script src="script.js"></script>
  </head>

  <body>
    <h1>Compile dynamic HTML</h1>
    <div ng-controller="MyController">
      <textarea ng-model="html"></textarea>
      <div dynamic="html"></div>
    </div>
  </body>

</html>

script.js

var app = angular.module('app', []);

app.directive('dynamic', function ($compile) {
  return {
    restrict: 'A',
    replace: true,
    link: function (scope, ele, attrs) {
      scope.$watch(attrs.dynamic, function(html) {
        ele.html(html);
        $compile(ele.contents())(scope);
      });
    }
  };
});

function MyController($scope) {
  $scope.click = function(arg) {
    alert('Clicked ' + arg);
  }
  $scope.html = '<a ng-click="click(1)" href="#">Click me</a>';
}
Buu Nguyen
fuente
66
Muchas gracias, Buu! Crear la directiva de atributos y agregar la función de observación del alcance fueron las dos cosas que me faltaba. Ahora que esto está funcionando, supongo que volveré a leer sobre las directivas y $ compilar, para comprender mejor lo que sucede debajo del capó.
giraffe_sense
11
¡Yo también! El equipo de Angular realmente podría mejorar los documentos sobre esto.
Craig Morgan
$compile(ele.contents())(scope);- esta línea resolvió mi problema de no compilar componentes angulares que se agregan dinámicamente. Gracias.
Mital Pritmani
@BuuNguyen dentro de teplateURL suponga que si incluye alguna página htmnl dinámica usando ng-bind-html, entonces usar compilar no funciona da error de algún contenido inseguro del otro lado usando trustAsHTml solo elimina el error inseguro no compila, ¿alguna sugerencia?
anam
1
Me gusta este ejemplo, pero no hace que el mío funcione. Tengo una declaración de cambio que ocurre debido a la elección del usuario, por lo que es dinámica. Dependiendo de eso, quiero insertar la directiva que contiene html. La directiva funciona si la coloco en la fase de arranque natural. Sin embargo, tengo esto que simplemente no se dispara --- case 'info': $ scope.htmlString = $ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>'); descanso; --- cuando quiero hacer algo como --- $ compile ($ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>')); Alguna idea sobre soluciones alternativas, etc ...
aterrizó el
19

En angular 1.2.10, la línea scope.$watch(attrs.dynamic, function(html) {devolvía un error de carácter no válido porque intentaba ver el valor del attrs.dynamiccual era texto html.

Lo arreglé recuperando el atributo de la propiedad del alcance

 scope: { dynamic: '=dynamic'}, 

Mi ejemplo

angular.module('app')
  .directive('dynamic', function ($compile) {
    return {
      restrict: 'A',
      replace: true,
      scope: { dynamic: '=dynamic'},
      link: function postLink(scope, element, attrs) {
        scope.$watch( 'dynamic' , function(html){
          element.html(html);
          $compile(element.contents())(scope);
        });
      }
    };
  });
Alexandros Spyropoulos
fuente
Hola, si uso element.html me devuelve TypeError: no se puede llamar al método 'insertBefore' de nulo. Entonces, después de buscar en Google, descubro que debo usar element.append Pero si uso esa directiva en varios lugares, generará HTML multiplicado. Entonces 2 directivas generan 4 mismos códigos HTML. Gracias por tu respuesta.
DzeryCZ
No usaría agregar en su lugar, lo examinaré esta noche y me pondré en contacto con usted. Para ser sincero, utilicé esta directiva en bastantes lugares de una página sin ningún problema. Intentaré reproducir el problema y te responderé.
Alexandros Spyropoulos
1
@AlexandrosSpyropoulos Acabo de probar y veo que mi código funciona bien incluso con 1.2.12. ¿Creo que probablemente te perdiste la declaración <div dynamic = "html"> en el HTML? (Con esa declaración, $ watch observa la propiedad 'html' en su alcance, no el HTML real como mencionó, por lo que no debería haber un error de char no válido). Si no, envíeme el plunkr que muestra que no funciona, yo Veré qué pasa.
Buu Nguyen
Probablemente tengas razón. En aquel entonces esperaba, que html es en realidad una variable que contiene html: P. Sin embargo, es una buena idea establecer un alcance en sus Directivas. umur.io/…
Alexandros Spyropoulos
$compile(ele.contents())(scope);- esta línea resolvió mi problema de no compilar componentes angulares que se agregan dinámicamente. Gracias.
Mital Pritmani
5

Encontrado en un grupo de discusión de google. Funciona para mi.

var $injector = angular.injector(['ng', 'myApp']);
$injector.invoke(function($rootScope, $compile) {
  $compile(element)($rootScope);
});
kwerle
fuente
3

Puedes usar

ng-bind-html https://docs.angularjs.org/api/ng/service/$sce

directiva para enlazar html dinámicamente. Sin embargo, debe obtener los datos a través del servicio $ sce.

Vea la demostración en vivo en http://plnkr.co/edit/k4s3Bx

var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$sce) {
    $scope.getHtml=function(){
   return $sce.trustAsHtml("<b>Hi Rupesh hi <u>dfdfdfdf</u>!</b>sdafsdfsdf<button>dfdfasdf</button>");
   }
});

  <body ng-controller="MainCtrl">
<span ng-bind-html="getHtml()"></span>
  </body>
Rupesh Kumar Tiwari
fuente
¡Gracias! Esto me ayudo. Sin embargo, debe incluir ngSanitize y angular-sanitize.js:var myApp = angular.module('myApp', ['ngSanitize']);
jaggedsoft
eso también funcionó para mí durante el enlace del ícono bootstrap al elemento material md-list span
changtung
1

Pruebe este código a continuación para vincular html a través de attr

.directive('dynamic', function ($compile) {
    return {
      restrict: 'A',
      replace: true,
      scope: { dynamic: '=dynamic'},
      link: function postLink(scope, element, attrs) {
        scope.$watch( 'attrs.dynamic' , function(html){
          element.html(scope.dynamic);
          $compile(element.contents())(scope);
        });
      }
    };
  });

Pruebe este element.html (scope.dynamic); que element.html (attr.dynamic);

Ramesh M
fuente