Compilation de chaînes HTML dynamiques à partir de la base de données

132

La situation

Une directive appelée Page, soutenue par un contrôleur, contient une div avec un attribut ng-bind-html-unsafe. Ceci est assigné à une var $ scope appelée 'pageContent'. Cette variable obtient un code HTML généré dynamiquement à partir d'une base de données. Lorsque l'utilisateur passe à la page suivante, un appel à la base de données est effectué et la variable pageContent est définie sur ce nouveau HTML, qui est rendu à l'écran via ng-bind-html-unsafe. Voici le code:

Directive de page

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

Modèle de la directive de page ("page.html" de la propriété templateUrl ci-dessus)

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

Contrôleur de page

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

});

Ça marche. Nous voyons le HTML de la page à partir de la base de données bien rendu dans le navigateur. Lorsque l'utilisateur passe à la page suivante, nous voyons le contenu de la page suivante, et ainsi de suite. Jusqu'ici tout va bien.

Le problème

Le problème ici est que nous voulons avoir un contenu interactif à l'intérieur du contenu d'une page. Par exemple, le HTML peut contenir une image miniature où, lorsque l'utilisateur clique dessus, Angular doit faire quelque chose de génial, comme afficher une fenêtre modale contextuelle. J'ai placé des appels de méthode Angular (ng-click) dans les chaînes HTML de notre base de données, mais bien sûr, Angular ne reconnaîtra ni les appels de méthode ni les directives à moins qu'il n'analyse la chaîne HTML, ne les reconnaisse et ne les compile.

Dans notre DB

Contenu de la page 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>

Contenu de la page 2:

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

De retour dans le contrôleur de page, nous ajoutons ensuite la fonction $ scope correspondante:

Contrôleur de page

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

Je ne peux pas comprendre comment appeler cette méthode 'doSomethingAwesome' à partir de la chaîne HTML de la base de données. Je me rends compte qu'Angular doit analyser la chaîne HTML d'une manière ou d'une autre, mais comment? J'ai lu de vagues marmonnements sur le service $ compile, et copié et collé quelques exemples, mais rien ne fonctionne. De plus, la plupart des exemples montrent que le contenu dynamique n'est défini que pendant la phase de liaison de la directive. Nous voudrions que Page reste en vie tout au long de la vie de l'application. Il reçoit, compile et affiche en permanence du nouveau contenu lorsque l'utilisateur parcourt les pages.

Dans un sens abstrait, je suppose que vous pourriez dire que nous essayons d'imbriquer dynamiquement des morceaux d'Angular dans une application Angular et que nous devons être en mesure de les échanger entre eux.

J'ai lu plusieurs fois plusieurs morceaux de documentation Angular, ainsi que toutes sortes de billets de blog et JS Fiddled with people code. Je ne sais pas si je ne comprends pas complètement Angular, ou si je manque simplement quelque chose de simple, ou peut-être que je suis lent. Dans tous les cas, je pourrais utiliser quelques conseils.

girafe_sense
la source
2
$ compile et les blogs de documentation qui l'entourent me font sentir que je suis aussi lent - même si j'ai l'impression que mon js est assez fort - je pense que si je m'y prends, je créerai un blog de style idiots - c'est ma spécialité!
atterri le

Réponses:

248

ng-bind-html-unsaferend uniquement le contenu au format HTML. Il ne lie pas la portée angulaire au DOM résultant. Vous devez utiliser le $compileservice à cette fin. J'ai créé ce plunker pour montrer comment l'utiliser $compilepour créer une directive rendant le HTML dynamique entré par les utilisateurs et lié à la portée du contrôleur. La source est publiée ci-dessous.

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
la source
6
Merci beaucoup, Buu! La création de la directive d'attribut et l'ajout de la fonction de surveillance de la portée étaient les deux choses qui me manquaient. Maintenant que cela fonctionne, je suppose que je vais relire les directives et $ compile, pour mieux comprendre ce qui se passe sous le capot.
giraffe_sense
11
Moi aussi, l'équipe Angular pourrait vraiment améliorer la documentation à ce sujet.
Craig Morgan
$compile(ele.contents())(scope);- cette ligne a résolu mon problème de ne pas compiler les composants angulaires qui sont ajoutés dynamiquement. Merci.
Mital Pritmani
@BuuNguyen à l'intérieur de teplateURL suppose que si vous incluez une page htmnl dynamique en utilisant ng-bind-html, alors l'utilisation de compile ne fonctionne pas donne une erreur à partir d'un contenu dangereux de l'autre côté en utilisant trustAsHTml ne supprime que l'erreur non sécurisée ne compile pas, des suggestions?
anam
1
J'aime cet exemple mais il ne fait pas fonctionner le mien. J'ai une instruction de commutation qui se produit en raison du choix de l'utilisateur, donc c'est dynamique. En fonction de cela, je veux insérer une directive contenant du html. La directive fonctionne si je la place dans la phase de bootstrap naturel. Cependant, j'ai ceci qui ne déclenche tout simplement pas --- cas 'info': $ scope.htmlString = $ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>'); Pause; --- quand je veux faire quelque chose comme --- $ compile ($ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>')); Toutes les idées sur les solutions de contournement, etc.
atterri le
19

Dans angular 1.2.10, la ligne scope.$watch(attrs.dynamic, function(html) {renvoyait une erreur de caractère non valide car elle essayait de regarder la valeur de attrs.dynamicqui était du texte html.

J'ai corrigé cela en récupérant l'attribut de la propriété scope

 scope: { dynamic: '=dynamic'}, 

Mon exemple

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
la source
Bonjour, Si je element.html il me retourner TypeError: Impossible d' appel de méthode « insertBefore » de null. Donc, après quelques recherches sur Google à ce sujet, je trouve que je dois utiliser element.append Mais si j'utilise cette directive à plusieurs endroits - cela génère du HTML multiplicateur. Donc 2 directives génèrent 4 même code HTML. Merci pour votre réponse.
DzeryCZ
Je n'utiliserais pas append à votre place, je jetterai un œil là-dessus ce soir et je vous répondrai. Pour être honnête, j'ai utilisé cette directive à de nombreux endroits d'une page sans aucun problème. Je vais essayer de reproduire le problème et je vous répondrai.
Alexandros Spyropoulos
1
@AlexandrosSpyropoulos Je viens de tester et de voir que mon code fonctionne bien même avec la version 1.2.12. Je pense que vous avez probablement manqué la déclaration <div dynamic = "html"> dans le HTML? (Avec cette déclaration, $ watch regarde la propriété 'html' dans la portée, pas le HTML réel comme vous l'avez mentionné, donc il ne devrait pas y avoir d'erreur de char invalide.) Sinon, envoyez-moi le plunkr qui montre que cela ne fonctionne pas, je Je vais voir ce qui ne va pas.
Buu Nguyen
Tu as probablement raison. Je m'attendais à l'époque à ce que html soit en fait une variable contenant html: P. C'est une bonne idée de définir un champ d'application pour vos directives. umur.io/…
Alexandros Spyropoulos
$compile(ele.contents())(scope);- cette ligne a résolu mon problème de ne pas compiler les composants angulaires qui sont ajoutés dynamiquement. Merci.
Mital Pritmani
5

Trouvé dans un groupe de discussion Google. Travaille pour moi.

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

Vous pouvez utiliser

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

directive pour lier le HTML dynamiquement. Cependant, vous devez obtenir les données via le service $ sce.

Veuillez voir la démo en direct sur 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
la source
Merci! Cela m'a aidé. Cependant, vous devez inclure ngSanitize et angular- var myApp = angular.module('myApp', ['ngSanitize']);
sanitize.js
cela a fonctionné pour moi aussi lors de la liaison de l'icône de bootstrap à l'élément span matériel md-list
changtung
1

Essayez ce code ci-dessous pour lier html via 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);
        });
      }
    };
  });

Essayez cet élément.html (scope.dynamic); que element.html (attr.dynamic);

Ramesh M
la source