Kompilowanie dynamicznych ciągów HTML z bazy danych

132

Sytuacja

W naszej aplikacji Angulara jest zagnieżdżona dyrektywa o nazwie Page, wspierana przez kontroler, która zawiera element div z atrybutem ng-bind-html-unsafe. Jest to przypisane do zmiennej $ scope o nazwie „pageContent”. Ta zmienna otrzymuje dynamicznie generowany kod HTML z bazy danych. Kiedy użytkownik przechodzi do następnej strony, następuje wywołanie bazy danych, a zmienna pageContent jest ustawiana na ten nowy kod HTML, który jest renderowany na ekranie przez ng-bind-html-unsafe. Oto kod:

Dyrektywa strony

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

Szablon dyrektywy strony („page.html” z właściwości templateUrl powyżej)

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

Kontroler strony

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

});

To działa. Widzimy, że kod HTML strony z bazy danych jest ładnie renderowany w przeglądarce. Kiedy użytkownik przechodzi do następnej strony, widzimy zawartość następnej strony i tak dalej. Na razie w porządku.

Problem

Problem polega na tym, że chcemy mieć interaktywną zawartość w treści strony. Na przykład kod HTML może zawierać miniaturę, w której po kliknięciu przez użytkownika Angular powinien zrobić coś niesamowitego, na przykład wyświetlić wyskakujące okienko modalne. Umieściłem wywołania metod Angular (ng-click) w łańcuchach HTML w naszej bazie danych, ale oczywiście Angular nie rozpozna ani wywołań metod, ani dyrektyw, chyba że w jakiś sposób przeanalizuje ciąg HTML, rozpozna je i skompiluje.

W naszej DB

Treść strony 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>

Treść strony 2:

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

W kontrolerze Page, dodajemy następnie odpowiednią funkcję $ scope:

Kontroler strony

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

Nie mogę dowiedzieć się, jak wywołać tę metodę „doSomethingAwesome” z ciągu znaków HTML z bazy danych. Zdaję sobie sprawę, że Angular musi jakoś przeanalizować ciąg HTML, ale jak? Przeczytałem niejasne mamrotania o usłudze $ compile, skopiowałem i wkleiłem kilka przykładów, ale nic nie działa. Ponadto większość przykładów pokazuje, że zawartość dynamiczna jest ustawiana tylko w fazie linkowania dyrektywy. Chcielibyśmy, aby Page działała przez cały okres użytkowania aplikacji. Stale otrzymuje, kompiluje i wyświetla nowe treści, gdy użytkownik przegląda strony.

W sensie abstrakcyjnym myślę, że można powiedzieć, że próbujemy dynamicznie zagnieżdżać fragmenty Angular w aplikacji Angular i musimy mieć możliwość ich wymiany.

Wielokrotnie czytałem różne fragmenty dokumentacji Angular, a także wszelkiego rodzaju posty na blogu i JS Fiddled z kodem ludzi. Nie wiem, czy kompletnie nie rozumiem Angulara, czy po prostu przeoczę coś prostego, czy może jestem wolny. W każdym razie przydałaby mi się rada.

giraffe_sense
źródło
2
$ compile i otaczające go blogi docs sprawiają, że czuję, że jestem również powolny - mimo że czuję, że mój js jest dość silny - myślę, że jeśli się z tym uporam, zrobię blog w stylu idiotów - to moja specjalność!
wylądował

Odpowiedzi:

249

ng-bind-html-unsafetylko renderuje zawartość jako HTML. Nie wiąże zakresu Angular z wynikowym DOM. W tym celu musisz skorzystać z $compileusługi. Stworzyłem ten plunker, aby zademonstrować, jak używać $compiledo tworzenia dyrektywy renderującej dynamiczny HTML wprowadzany przez użytkowników i wiążący się z zakresem kontrolera. Źródło jest zamieszczone poniżej.

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
źródło
6
Wielkie dzięki, Buu! Utworzenie dyrektywy atrybutu i dodanie funkcji monitorowania zakresu to dwie rzeczy, których mi brakowało. Teraz, gdy to działa, przypuszczam, że przeczytam ponownie o dyrektywach i kompilacji $ compile, aby lepiej zrozumieć, co się dzieje pod maską.
giraffe_sense
11
Ja też! Zespołowi Angulara przydałaby się poprawa dokumentacji na ten temat.
Craig Morgan
$compile(ele.contents())(scope);- ta linia rozwiązała mój problem z niekompilowaniem komponentów kątowych, które są dodawane dynamicznie. Dzięki.
Mital Pritmani,
@BuuNguyen wewnątrz teplateURL przypuśćmy, że jeśli włączysz jakąś dynamiczną stronę htmnl za pomocą ng-bind-html, to użycie kompilacji nie działa, powoduje błąd z niebezpiecznej zawartości drugiej strony za pomocą trustAsHTml tylko usuń niebezpieczny błąd nie kompiluje, jakieś sugestie?
anam
1
Podoba mi się ten przykład, ale mój nie działa. Mam instrukcję przełącznika, która dzieje się z powodu wyboru użytkownika, więc jest dynamiczna. W zależności od tego chcę wstawić dyrektywę html zawierającą. Dyrektywa działa, jeśli umieszczę ją w naturalnej fazie ładowania początkowego. Jednak mam ten, który po prostu się nie uruchamia --- case 'info': $ scope.htmlString = $ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>'); złamać; --- kiedy chcę zrobić coś takiego jak --- $ compile ($ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>')); Wszelkie pomysły na obejście problemu itp ...
wylądował
19

W kątowym 1.2.10 linia zwracała scope.$watch(attrs.dynamic, function(html) {błąd nieprawidłowego znaku, ponieważ próbowała obserwować wartość, attrs.dynamicktórej był tekst html.

Naprawiłem to, pobierając atrybut z właściwości zakresu

 scope: { dynamic: '=dynamic'}, 

Mój przykład

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
źródło
Witam, jeśli używam element.html zwraca mi to TypeError: Nie można wywołać metody „insertBefore” o wartości null. Więc po pewnym googlowaniu na ten temat stwierdzam, że muszę użyć element.append Ale jeśli używam tej dyrektywy w wielu miejscach - generuje ona multiplikowany HTML. Tak więc 2 dyrektywy generują 4 takie same kody HTML. Dzięki za odpowiedź.
DzeryCZ
Nie użyłbym dopisywania na twoim miejscu, spojrzę na to dziś wieczorem i wrócę do ciebie. Szczerze mówiąc, bez problemu zastosowałem tę dyrektywę w kilku miejscach na stronie. Spróbuję odtworzyć problem i skontaktuję się z tobą.
Alexandros Spyropoulos
1
@AlexandrosSpyropoulos Po prostu testuję i widzę, że mój kod działa dobrze nawet z 1.2.12. Myślę, że prawdopodobnie przegapiłeś deklarację <div dynamic = "html"> w kodzie HTML? (Z tą deklaracją $ watch obserwuje właściwość „html” w zakresie, a nie rzeczywisty kod HTML, jak wspomniałeś, więc nie powinno być nieprawidłowego błędu znaku.) Jeśli nie, wyślij mi plunkr, który pokazuje, że nie działa, ja Zobaczę, co się stało.
Buu Nguyen
Pewnie masz rację. Spodziewałem się wtedy, że html jest właściwie zmienną zawierającą html: P. Jednak dobrym pomysłem jest ustawienie zakresu dla dyrektyw. umur.io/…
Alexandros Spyropoulos
$compile(ele.contents())(scope);- ta linia rozwiązała mój problem z niekompilowaniem komponentów kątowych, które są dodawane dynamicznie. Dzięki.
Mital Pritmani,
5

Znaleziono w grupie dyskusyjnej Google. Pracuje dla mnie.

var $injector = angular.injector(['ng', 'myApp']);
$injector.invoke(function($rootScope, $compile) {
  $compile(element)($rootScope);
});
kwerle
źródło
3

Możesz użyć

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

dyrektywa do dynamicznego wiązania html. Jednak musisz uzyskać dane za pośrednictwem usługi $ sce.

Zobacz demo na żywo pod adresem 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
źródło
Dzięki! To mi pomogło. Musisz jednak dołączyć ngSanitize i angular-sanitize.js:var myApp = angular.module('myApp', ['ngSanitize']);
jaggedsoft
to też zadziałało podczas wiązania ikony bootstrap z materialnym elementem md-list span
changtung Kwietnia
1

Wypróbuj poniższy kod, aby powiązać HTML za pomocą 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);
        });
      }
    };
  });

Wypróbuj ten element.html (scope.dynamic); niż element.html (attr.dynamic);

Ramesh M.
źródło