Compilare stringhe HTML dinamiche dal database


132

La situazione

Nella nostra app Angular è nidificata una direttiva chiamata Page, supportata da un controller, che contiene un div con un attributo ng-bind-html-unsafe. Questo è assegnato a una var $ scope chiamata 'pageContent'. A questa var viene assegnato HTML generato dinamicamente da un database. Quando l'utente passa alla pagina successiva, viene effettuata una chiamata al DB e pageContent var viene impostato su questo nuovo HTML, che viene reso sullo schermo tramite ng-bind-html-unsafe. Ecco il codice:

Direttiva sulla pagina

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

Modello della direttiva di pagina ("page.html" dalla proprietà templateUrl sopra)

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

Controller di pagina

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

});

Che funzioni. Vediamo l'HTML della pagina dal DB reso bene nel browser. Quando l'utente passa alla pagina successiva, vediamo il contenuto della pagina successiva e così via. Fin qui tutto bene.

Il problema

Il problema qui è che vogliamo avere contenuti interattivi all'interno del contenuto di una pagina. Ad esempio, l'HTML può contenere un'immagine in miniatura in cui, quando l'utente fa clic su di essa, Angular dovrebbe fare qualcosa di fantastico, come la visualizzazione di una finestra modale pop-up. Ho inserito chiamate di metodo Angular (ng-click) nelle stringhe HTML nel nostro database, ma ovviamente Angular non riconoscerà né chiamate di metodo né direttive a meno che non analizzi in qualche modo la stringa HTML, le riconosca e le compili.

Nel nostro DB

Contenuto per la pagina 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>

Contenuto per Pagina 2:

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

Di nuovo nel controller Pagina, aggiungiamo la funzione $ scope corrispondente:

Controller di pagina

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

Non riesco a capire come chiamare quel metodo 'doSomethingAwesome' dall'interno della stringa HTML dal DB. Mi rendo conto che Angular deve analizzare la stringa HTML in qualche modo, ma come? Ho letto vaghi borbottii sul servizio $ compilare, copiato e incollato alcuni esempi, ma nulla funziona. Inoltre, la maggior parte degli esempi mostra che il contenuto dinamico viene impostato solo durante la fase di collegamento della direttiva. Vorremmo che Page rimanesse vivo per tutta la vita dell'app. Riceve, compila e visualizza costantemente nuovi contenuti mentre l'utente scorre le pagine.

In senso astratto, suppongo che potresti dire che stiamo cercando di nidificare in modo dinamico blocchi di angolari all'interno di un'app angolare e che dobbiamo essere in grado di scambiarli dentro e fuori.

Ho letto varie parti della documentazione angolare più volte, così come tutti i tipi di post sul blog, e JS ha manipolato il codice delle persone. Non so se sto completamente fraintendendo Angular, o semplicemente mancando qualcosa di semplice, o forse sono lento. In ogni caso, potrei usare qualche consiglio.


2
$ compile e i blog di documenti che lo circondano mi fanno sentire che sono anche lento - anche se sento che il mio js è abbastanza forte - penso che se dovrò fare i conti con questo farei un blog in stile idioti - questa è la mia specialità!
Atterrato il

Risposte:


248

ng-bind-html-unsafeesegue il rendering del contenuto solo come HTML. Non vincola l'ambito angolare al DOM risultante. Devi usare il $compileservizio per quello scopo. Ho creato questo plunker per dimostrare come utilizzare $compileper creare una direttiva che renda HTML dinamico immesso dagli utenti e vincolante all'ambito del controller. La fonte è pubblicata di seguito.

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
Grazie mille, Buu! La creazione della direttiva sugli attributi e l'aggiunta della funzione watch scope erano le due cose che mi mancavano. Ora che funziona, suppongo che rileggerò le direttive e la compilazione di $, per capire meglio cosa sta succedendo sotto il cofano.
giraffe_sense

11
Anche a me! Il team di Angular potrebbe davvero fare con il miglioramento dei documenti su questo.
Craig Morgan,

$compile(ele.contents())(scope);- questa riga ha risolto il mio problema di non compilare componenti angolari che vengono aggiunti in modo dinamico. Grazie.
Mital Pritmani,

@BuuNguyen all'interno di teplateURL supponiamo che tu includa una pagina htmnl dinamica usando ng-bind-html, quindi l'uso della compilazione non funziona dà errore da alcuni contenuti non sicuri dall'altra parte usando trustAsHTml rimuovi solo errori non sicuri non viene compilata, qualche suggerimento?
anam

1
Mi piace questo esempio ma non fa funzionare il mio. Ho una dichiarazione switch che accade a causa della scelta dell'utente, quindi è dinamica. A seconda di ciò, voglio inserire la direttiva contenente HTML. La direttiva funziona se la inserisco nella fase di bootstrap naturale. Tuttavia ho questo che semplicemente non si attiva --- case 'info': $ scope.htmlString = $ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>'); rompere; --- quando voglio fare qualcosa come --- $ compile ($ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>')); Qualche idea su soluzioni alternative ecc ...
atterrato il

19

Nell'angolo 1.2.10 la riga scope.$watch(attrs.dynamic, function(html) {stava restituendo un errore di carattere non valido perché stava cercando di vedere il valore di attrs.dynamiccui era testo html.

L'ho risolto recuperando l'attributo dalla proprietà scope

 scope: { dynamic: '=dynamic'}, 

Il mio esempio

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

Ciao, se uso element.html mi restituisce TypeError: Impossibile chiamare il metodo 'insertBefore' di null. Quindi, dopo aver cercato su Google che ho scoperto che devo usare element.append Ma se uso quella direttiva in più punti, genera HTML moltiplicato. Quindi 2 direttive generano 4 stesso codice HTML. Grazie per la tua risposta.
DzeryCZ,

Non userei append al tuo posto, lo guarderò stasera e torno da te. Ad essere sincero, ho usato questa direttiva in diversi punti di una pagina senza alcun problema. Proverò a riprodurre il problema e ti ricontatterò.
Alexandros Spyropoulos,

1
@AlexandrosSpyropoulos Ho appena testato e ho visto che il mio codice funziona bene anche con 1.2.12. Penso che probabilmente hai perso la dichiarazione <div dynamic = "html"> in HTML? (Con quella dichiarazione, $ watch osserva la proprietà 'html' nell'ambito, non l'HTML effettivo come hai menzionato, quindi non dovrebbe esserci un errore char non valido.) In caso contrario, inviami il plunkr che mostra che non funziona, I vedrò cosa c'è che non va.
Buu Nguyen,

Probabilmente hai ragione. Allora mi aspettavo che HTML fosse in realtà una variabile che contiene HTML: P. È comunque una buona idea stabilire un ambito di applicazione per le vostre direttive. umur.io/…
Alexandros Spyropoulos

$compile(ele.contents())(scope);- questa riga ha risolto il mio problema di non compilare componenti angolari che vengono aggiunti in modo dinamico. Grazie.
Mital Pritmani,

5

Trovato in un gruppo di discussione di Google. Per me va bene.

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

3

Puoi usare

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

direttiva per legare HTML in modo dinamico. Tuttavia devi ottenere i dati tramite il servizio $ sce.

Si prega di consultare la demo dal vivo su 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>

Grazie! Questo mi ha aiutato. Tuttavia, è necessario includere ngSanitize e angular-sanitize.js:var myApp = angular.module('myApp', ['ngSanitize']);
jaggedsoft,

che ha funzionato anche per me durante l'associazione dell'icona bootstrap all'elemento materiale span md-list
changtung

1

Prova questo codice qui sotto per associare html tramite 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);
        });
      }
    };
  });

Prova questo element.html (scope.dynamic); di element.html (attr.dynamic);

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.