Kompilieren dynamischer HTML-Zeichenfolgen aus der Datenbank

132

Die Situation

In unserer Angular-App ist eine Direktive namens Page verschachtelt, die von einem Controller unterstützt wird und ein div mit dem Attribut ng-bind-html-unsafe enthält. Dies wird einer $ scope-Variable namens 'pageContent' zugewiesen. Dieser Variable wird dynamisch generiertes HTML aus einer Datenbank zugewiesen. Wenn der Benutzer zur nächsten Seite blättert, wird ein Aufruf an die Datenbank vorgenommen, und die Variable pageContent wird auf diesen neuen HTML-Code gesetzt, der über ng-bind-html-unsafe auf dem Bildschirm gerendert wird. Hier ist der Code:

Seitenrichtlinie

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

Vorlage der Seitenanweisung ("page.html" aus der Eigenschaft templateUrl oben)

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

Seitencontroller

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

});

Das funktioniert. Wir sehen den HTML-Code der Seite aus der Datenbank im Browser gut gerendert. Wenn der Benutzer zur nächsten Seite wechselt, wird der Inhalt der nächsten Seite usw. angezeigt. So weit, ist es gut.

Das Problem

Das Problem hierbei ist, dass wir interaktiven Inhalt innerhalb des Inhalts einer Seite haben möchten. Beispielsweise kann der HTML-Code ein Miniaturbild enthalten, in dem Angular, wenn der Benutzer darauf klickt, etwas Fantastisches tun sollte, z. B. das Anzeigen eines modalen Popup-Fensters. Ich habe Angular-Methodenaufrufe (ng-click) in die HTML-Zeichenfolgen in unserer Datenbank eingefügt, aber Angular erkennt natürlich weder Methodenaufrufe noch Direktiven, es sei denn, es analysiert die HTML-Zeichenfolge, erkennt sie und kompiliert sie.

In unserer DB

Inhalt für Seite 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>

Inhalt für Seite 2:

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

Zurück im Page-Controller fügen wir dann die entsprechende $ scope-Funktion hinzu:

Seitencontroller

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

Ich kann nicht herausfinden, wie diese 'doSomethingAwesome'-Methode aus der HTML-Zeichenfolge der Datenbank heraus aufgerufen werden kann. Mir ist klar, dass Angular die HTML-Zeichenfolge irgendwie analysieren muss, aber wie? Ich habe vage Gemurmel über den $ compile-Dienst gelesen und einige Beispiele kopiert und eingefügt, aber nichts funktioniert. Außerdem zeigen die meisten Beispiele, dass dynamischer Inhalt nur während der Verknüpfungsphase der Direktive festgelegt wird. Wir möchten, dass Page während des gesamten Lebens der App am Leben bleibt. Es empfängt, kompiliert und zeigt ständig neue Inhalte an, während der Benutzer durch die Seiten blättert.

In einem abstrakten Sinne könnte man sagen, dass wir versuchen, Angular-Blöcke dynamisch in einer Angular-App zu verschachteln und sie ein- und austauschen müssen.

Ich habe verschiedene Teile der Angular-Dokumentation mehrmals gelesen sowie alle Arten von Blog-Posts und JS hat mit dem Code der Leute herumgespielt. Ich weiß nicht, ob ich Angular völlig falsch verstehe oder nur etwas Einfaches vermisse oder ob ich langsam bin. Auf jeden Fall könnte ich einen Rat gebrauchen.

giraffe_sense
quelle
2
$ compile und die dazugehörigen Docs-Blogs geben mir das Gefühl, dass ich auch langsam bin - obwohl ich das Gefühl habe, dass mein js ziemlich stark ist - ich denke, wenn ich mich damit auseinandersetze, werde ich einen Blog im Idiotenstil erstellen - das ist meine Spezialität!
landete am

Antworten:

248

ng-bind-html-unsaferendert den Inhalt nur als HTML. Der Angular-Bereich wird nicht an das resultierende DOM gebunden. Zu $compilediesem Zweck müssen Sie den Dienst nutzen. Ich habe diesen Plunker erstellt , um zu demonstrieren, wie $compileeine Direktive erstellt wird, die von Benutzern eingegebenes dynamisches HTML rendert und an den Bereich des Controllers bindet. Die Quelle ist unten angegeben.

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
quelle
6
Vielen Dank, Buu! Das Erstellen der Attribut-Direktive und das Hinzufügen der Scope-Watch-Funktion waren die beiden Dinge, die mir fehlten. Nun, da dies funktioniert, werde ich wohl noch einmal die Anweisungen und $ compile nachlesen, um besser zu verstehen, was unter der Haube vor sich geht.
Giraffe_sense
11
Ich auch! Das Angular-Team könnte es wirklich gebrauchen, die Dokumentation dazu zu verbessern.
Craig Morgan
$compile(ele.contents())(scope);- Diese Zeile löste mein Problem, keine Winkelkomponenten zu kompilieren, die dynamisch hinzugefügt werden. Vielen Dank.
Mital Pritmani
@BuuNguyen in teplateURL Angenommen, wenn Sie eine dynamische HTML-Seite mit ng-bind-html einschließen, dann funktioniert die Verwendung von compile nicht.
Anam
1
Ich mag dieses Beispiel, aber es bringt mich nicht zum Laufen. Ich habe eine switch-Anweisung, die aufgrund der Benutzerauswahl geschieht, also dynamisch. Abhängig davon möchte ich HTML mit der Direktive einfügen. Die Direktive funktioniert, wenn ich sie in die natürliche Bootstrap-Phase stelle. Allerdings habe ich dies, das einfach nicht ausgelöst wird --- case 'info': $ scope.htmlString = $ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </ div>'); brechen; --- wenn ich so etwas wie --- $ compile ($ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </ div>')) machen möchte; Irgendwelche Ideen zu Problemumgehungen usw.
landete am
19

In Winkel 1.2.10 gab die Zeile scope.$watch(attrs.dynamic, function(html) {einen ungültigen Zeichenfehler zurück, da versucht wurde, den Wert von attrs.dynamicHTML-Text zu überwachen .

Ich habe das behoben, indem ich das Attribut aus der Eigenschaft scope abgerufen habe

 scope: { dynamic: '=dynamic'}, 

Mein Beispiel

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
quelle
Hallo, wenn ich element.html verwende , wird mir TypeError zurückgegeben: Die Methode 'insertBefore' von null kann nicht aufgerufen werden. Nach einigem googeln stelle ich fest, dass ich element.append verwenden muss. Wenn ich diese Direktive jedoch an mehreren Stellen verwende, wird multipliziertes HTML generiert. 2 Anweisungen generieren also 4 gleichen HTML-Code. Danke für deine Antwort.
DzeryCZ
Ich würde an Ihrer Stelle kein Anhängen verwenden, ich werde mir das heute Abend ansehen und mich bei Ihnen melden. Um ehrlich zu sein, habe ich diese Richtlinie an einigen Stellen auf einer Seite ohne Probleme verwendet. Ich werde versuchen, das Problem zu reproduzieren, und ich werde mich bei Ihnen melden.
Alexandros Spyropoulos
1
@AlexandrosSpyropoulos Ich teste nur und sehe, dass mein Code auch mit 1.2.12 in Ordnung läuft. Ich denke, Sie haben wahrscheinlich die Deklaration <div dynamic = "html"> im HTML verpasst? (Mit dieser Deklaration überwacht $ watch die 'html'-Eigenschaft im Gültigkeitsbereich und nicht den tatsächlichen HTML-Code, wie Sie erwähnt haben. Es sollte also kein ungültiger Zeichenfehler auftreten.) Wenn nicht, senden Sie mir den Plunkr, der anzeigt, dass er nicht funktioniert Ich werde sehen, was los ist.
Buu Nguyen
Du hast wahrscheinlich Recht. Ich habe damals erwartet, dass HTML tatsächlich eine Variable ist, die HTML enthält: P. Es ist jedoch eine gute Idee, einen Geltungsbereich für Ihre Richtlinien festzulegen. umur.io/…
Alexandros Spyropoulos
$compile(ele.contents())(scope);- Diese Zeile löste mein Problem, keine Winkelkomponenten zu kompilieren, die dynamisch hinzugefügt werden. Vielen Dank.
Mital Pritmani
5

Gefunden in einer Google-Diskussionsgruppe. Funktioniert bei mir.

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

Sie können verwenden

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

Direktive zum dynamischen Binden von HTML. Sie müssen die Daten jedoch über den Dienst $ sce abrufen.

Bitte sehen Sie die Live-Demo unter 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
quelle
Vielen Dank! Das hat mir geholfen. Sie müssen jedoch ngSanitize und angle- var myApp = angular.module('myApp', ['ngSanitize']);
sanitize.js einschließen
Das funktionierte auch für mich beim Binden des Bootstrap-Symbols an das Material-
MD
1

Versuchen Sie diesen Code unten, um HTML über attr zu binden

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

Versuchen Sie diese element.html (scope.dynamic); als element.html (attr.dynamic);

Ramesh M.
quelle