Compilando seqüências HTML dinâmicas do banco de dados

132

A situação

Aninhada em nosso aplicativo Angular está uma diretiva chamada Page, apoiada por um controlador, que contém uma div com um atributo ng-bind-html-unsafe. Isso é atribuído a uma variável $ scope chamada 'pageContent'. Essa var recebe o HTML gerado dinamicamente a partir de um banco de dados. Quando o usuário muda para a página seguinte, é feita uma chamada ao banco de dados e a pageContent var é definida para esse novo HTML, que é renderizado na tela por meio de ng-bind-html-unsafe. Aqui está o código:

Diretiva de página

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

Modelo da diretiva de página ("page.html" da propriedade templateUrl acima)

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

Controlador de página

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

});

Isso funciona. Vemos o HTML da página do banco de dados renderizado de maneira agradável no navegador. Quando o usuário passa para a próxima página, vemos o conteúdo da próxima página e assim por diante. Por enquanto, tudo bem.

O problema

O problema aqui é que queremos ter conteúdo interativo dentro do conteúdo de uma página. Por exemplo, o HTML pode conter uma imagem em miniatura na qual, quando o usuário clica nela, o Angular deve fazer algo impressionante, como exibir uma janela modal pop-up. Coloquei chamadas de método Angular (ng-click) nas seqüências HTML em nosso banco de dados, mas é claro que o Angular não reconhecerá chamadas ou diretivas de método, a menos que de alguma forma analise a sequência HTML, as reconheça e as compile.

No nosso banco de dados

Conteúdo da página 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>

Conteúdo da página 2:

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

De volta ao controlador de página, adicionamos a função $ scope correspondente:

Controlador de página

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

Não consigo descobrir como chamar esse método 'doSomethingAwesome' de dentro da string HTML do banco de dados. Sei que Angular precisa analisar a string HTML de alguma forma, mas como? Eu li vagos murmúrios sobre o serviço de compilação $, copiei e colei alguns exemplos, mas nada funciona. Além disso, a maioria dos exemplos mostra o conteúdo dinâmico sendo definido apenas durante a fase de vinculação da diretiva. Queremos que Page permaneça vivo durante toda a vida do aplicativo. Ele constantemente recebe, compila e exibe novo conteúdo à medida que o usuário percorre as páginas.

Em um sentido abstrato, acho que você poderia dizer que estamos tentando aninhar dinamicamente pedaços de Angular em um aplicativo Angular e precisamos ser capazes de trocá-los.

Li várias partes da documentação do Angular várias vezes, além de todos os tipos de postagens no blog, e o JS brincou com o código das pessoas. Não sei se estou entendendo completamente o Angular, ou apenas perdendo algo simples, ou talvez seja lento. De qualquer forma, eu poderia usar alguns conselhos.

giraffe_sense
fonte
2
O $ compile e os blogs de documentos que o cercam me fazem sentir que também sou lento - mesmo que eu sinta que meu js é bastante forte - acho que, se eu entender isso, farei um blog de estilo idiota - essa é minha especialidade!
desembarcou em

Respostas:

248

ng-bind-html-unsaferenderiza apenas o conteúdo como HTML. Não vincula o escopo Angular ao DOM resultante. Você precisa usar o $compileserviço para esse fim. Eu criei esse plunker para demonstrar como usar $compilepara criar uma diretiva que processe HTML dinâmico inserido pelos usuários e vincule-o ao escopo do controlador. A fonte está publicada abaixo.

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
fonte
6
Muito obrigado, Buu! Criar a diretiva de atributo e adicionar a função de observação do escopo eram as duas coisas que estavam faltando. Agora que isso está funcionando, acho que vou ler novamente as diretivas e $ compile, para entender melhor o que está acontecendo sob o capô.
Giraffe_sense 12/08/2013
11
Eu também! A equipe da Angular poderia realmente melhorar a documentação.
Craig Morgan
$compile(ele.contents())(scope);- esta linha resolveu meu problema de não compilar componentes angulares que são adicionados dinamicamente. Obrigado.
Mital Pritmani
@BuuNguyen dentro do teplateURL suponha que se você incluir alguma página htmnl dinâmica usando ng-bind-html, o uso de compilação não funciona gera erro de algum conteúdo não seguro do outro lado usando o trustAsHTml apenas remove o erro não seguro não compila, alguma sugestão?
Anam
1
Eu gosto deste exemplo, mas ele não funciona. Eu tenho uma instrução switch que ocorre devido à escolha do usuário, portanto é dinâmica. Dependendo disso, quero inserir a diretiva contendo html. A diretiva funciona se eu a colocar na fase de inicialização natural. No entanto, tenho este que simplesmente não está disparando --- case 'info': $ scope.htmlString = $ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>'); pausa; --- quando eu quero fazer algo como --- $ compile ($ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>')); Alguma idéia de soluções alternativas, etc ...
chegou em
19

No angular 1.2.10, a linha scope.$watch(attrs.dynamic, function(html) {estava retornando um erro de caractere inválido porque estava tentando observar o valor deattrs.dynamic qual era texto html.

Corrigi isso buscando o atributo na propriedade scope

 scope: { dynamic: '=dynamic'}, 

Meu exemplo

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
fonte
Olá, Se eu usar element.html, ele retornará TypeError: Não é possível chamar o método 'insertBefore' de null. Então, depois de pesquisar no Google, acho que devo usar o element.append Mas se eu usar essa diretiva em vários locais - ela gera HTML multiplicado. Então, duas diretivas geram 4 mesmo código HTML. Obrigado pela sua resposta.
DzeryCZ
Eu não usaria anexos em seu lugar, vou dar uma olhada nisso hoje à noite e eu voltarei para você. Para ser sincero, usei essa diretiva em muitos lugares de uma página sem nenhum problema. Vou tentar reproduzir o problema e volto para você.
Alexandros Spyropoulos
1
@AlexandrosSpyropoulos Acabei de testar e ver se meu código funciona bem mesmo com o 1.2.12. Eu acho que você provavelmente perdeu a declaração <div dynamic = "html"> no HTML? (Com essa declaração, $ watch observa a propriedade 'html' no escopo, não o HTML real, como você mencionou, portanto, não deve haver erro de caractere inválido.) Caso contrário, envie-me o plunkr que mostra que não funciona, eu vou ver o que está errado.
Buu Nguyen 31/03
Você provavelmente está certo. Eu esperava, naquela época, que o html fosse realmente uma variável que contenha html: P. É uma boa idéia, porém, definir um escopo para suas diretivas. umur.io/…
Alexandros Spyropoulos
$compile(ele.contents())(scope);- esta linha resolveu meu problema de não compilar componentes angulares que são adicionados dinamicamente. Obrigado.
Mital Pritmani
5

Encontrado em um grupo de discussão do Google. Funciona para mim.

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

Você pode usar

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

diretiva para vincular html dinamicamente. No entanto, você precisa obter os dados pelo serviço $ sce.

Por favor, veja a demonstração ao vivo em 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
fonte
Obrigado! Isso me ajudou. No entanto, você precisa incluir ngSanitize e angular-sanitize.js:var myApp = angular.module('myApp', ['ngSanitize']);
jaggedsoft
que trabalhou para mim também durante ícone de inicialização ligação a md-list material de elemento span
changtung
1

Tente este código abaixo para vincular o html através do 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);
        });
      }
    };
  });

Experimente este element.html (scope.dynamic); do que element.html (attr.dynamic);

Ramesh M
fonte