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.


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="angular.js@1.0.7" 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>';
}

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

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

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>

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.