データベースからの動的HTML文字列のコンパイル


132

状況

私たちのAngularアプリ内にネストされているのは、ng-bind-html-unsafe属性を持つdivを含む、コントローラーによってサポートされるPageというディレクティブです。これは、「pageContent」という$ scope変数に割り当てられます。この変数には、データベースから動的に生成されたHTMLが割り当てられます。ユーザーが次のページに移動すると、DBが呼び出され、pageContent変数がこの新しいHTMLに設定され、ng-bind-html-unsafeを介して画面上にレンダリングされます。これがコードです:

ページディレクティブ

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

ページディレクティブのテンプレート(上記のtemplateUrlプロパティの "page.html")

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

ページコントローラー

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

});

うまくいきます。ブラウザーで適切にレンダリングされたDBからのページのHTMLが表示されます。ユーザーが次のページに移動すると、次のページのコンテンツが表示されます。ここまでは順調ですね。

問題

ここでの問題は、ページのコンテンツ内にインタラクティブなコンテンツを含めることです。たとえば、HTMLにはサムネイル画像が含まれている可能性があり、ユーザーがクリックすると、Angularはポップアップモーダルウィンドウの表示など、すばらしい機能を実行します。私はデータベースのHTML文字列にAngularメソッド呼び出し(ng-click)を配置しましたが、もちろん、Angularは、何らかの方法でHTML文字列を解析して認識し、コンパイルしない限り、メソッド呼び出しまたはディレクティブを認識しません。

私たちのDBで

ページ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>

ページ2のコンテンツ:

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

ページコントローラに戻り、対応する$ scope関数を追加します。

ページコントローラー

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

DBのHTML文字列内から「doSomethingAwesome」メソッドを呼び出す方法がわかりません。AngularはHTML文字列をどうにかして解析しなければならないことを理解していますが、どうやって?$ compileサービスに関する漠然とした不平を読み、いくつかの例をコピーして貼り付けましたが、何も機能しません。また、ほとんどの例では、ディレクティブのリンク段階でのみ動的コンテンツが設定されることを示しています。アプリの存続期間中、Pageを存続させたいと思います。ユーザーがページをめくると、常に新しいコンテンツを受け取り、コンパイルして表示します。

抽象的な意味では、Angularアプリ内でAngularのチャンクを動的にネストしようとしているので、それらを入れ替えできるようにする必要があると言えます。

Angularのさまざまなドキュメントや、あらゆる種類のブログ投稿、JSのコードを何度も読んだことがあります。Angularを完全に誤解しているのか、単純なものを見逃しているのか、遅いのかわかりません。いずれにせよ、私はいくつかのアドバイスを使用することができます。


2
$ compileとそれを取り巻くドキュメントブログは、私も遅いと感じさせます-私のjsは非常に強いと感じていますが-これを理解できれば、ばかスタイルのブログを作るでしょう-それが私の専門です!
2015年

回答:


248

ng-bind-html-unsafeコンテンツはHTMLとしてのみレンダリングされます。Angularスコープを結果のDOMにバインドしません。$compileそのためにはサービスを利用する必要があります。使用方法を示すためにこのプランカーを作成しました$compileディレクティブレンダリングダイナミックHTMLのユーザーが入力したとコントローラのスコープへの結合を作成します。ソースは以下に掲載されています。

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
どうもありがとう、ブウ!属性ディレクティブの作成とスコープ監視関数の追加は、私が欠けていた2つのことでした。これが機能しているので、内部で何が行われているのかをよりよく理解するために、ディレクティブと$ compileについてもう一度読んでみましょう。
giraffe_sense 2013

11
私もそうです!Angularチームは、これに関するドキュメントの改善に本当に貢献できました。
クレイグモーガン

$compile(ele.contents())(scope);-この行は、動的に追加される角度コンポーネントをコンパイルしないという私の問題を解決しました。ありがとう。
Mital Pritmani 2014

teplateURL内の@BuuNguyenが、ng-bind-htmlを使用して動的htmnlページを含めた場合、コンパイルを使用しても機能しませんが、trustAsHTmlを使用して安全でないコンテンツからエラーが発生し、安全でないエラーを削除するだけで、コンパイルしないでください。
anam 2014年

1
この例は好きですが、うまくいきません。ユーザーの選択により動的に実行されるswitchステートメントがあります。それに応じて、htmlを含むディレクティブを挿入します。ディレクティブは、自然なブートストラップフェーズに配置すると機能します。ただし、これは単に起動しないだけです---ケース 'info':$ scope.htmlString = $ sce.trustAsHtml( '<div dynamic = "htmlString"> dddzzz </ div>'); ブレーク; ---次のようなことをしたい場合--- $ compile($ sce.trustAsHtml( '<div dynamic = "htmlString"> dddzzz </ div>')); 回避策などに関するアイデア
着陸

19

角度1.2.10では、ラインscope.$watch(attrs.dynamic, function(html) {は値を監視しようとしたため、無効な文字エラーを返していました。attrs.dynamic HTMLテキストある。

スコーププロパティから属性をフェッチして修正しました

 scope: { dynamic: '=dynamic'}, 

私の例

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

こんにちは、element.htmlを使用すると、TypeErrorが返されます。nullのメソッド 'insertBefore'を呼び出せません。したがって、それについてグーグルで調べた後、element.appendを使用する必要があることがわかりましたが、そのディレクティブを複数の場所で使用すると、複数のHTMLが生成されます。したがって、2つのディレクティブが4つの同じHTMLコードを生成します。ご回答有難うございます。
DzeryCZ 14

私はあなたの場所で追加を使用しません、今夜それを見ていきますので、あなたに戻ります。正直なところ、このディレクティブをページのかなりの数の場所で問題なく使用しました。問題の再現を試み、折り返しご連絡いたします。
Alexandros Spyropoulos 2014年

1
@AlexandrosSpyropoulos私はちょうどテストし、1.2.12でもコードが正常に実行されることを確認します。HTMLの宣言<div dynamic = "html">を見逃したと思いますか?(その宣言を使用すると、$ watchはスコープ内の 'html'プロパティを監視します。前述の実際のHTMLではないため、無効なcharエラーは発生しません。)そうでない場合は、機能しないことを示すplunkrを送ってください。何が悪いのかわかるでしょう。
ブウグエン

あなたはおそらく正しいです。私は当時期待していた、そのhtmlは実際にはhtml:Pを含む変数です。ただし、ディレクティブにスコープを設定することをお勧めします。 umur.io/...
アレクサンドロスSpyropoulos

$compile(ele.contents())(scope);-この行は、動的に追加される角度コンポーネントをコンパイルしないという私の問題を解決しました。ありがとう。
Mital Pritmani 2014

5

googleディスカッショングループで見つかりました。私のために働く。

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

3

使用できます

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

HTMLを動的にバインドするディレクティブ。ただし、$ sceサービスを介してデータを取得する必要があります。

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>

ありがとう!これは私を助けました。ただし、ngSanitizeと角度-sanitize.jsを含める必要があります:var myApp = angular.module('myApp', ['ngSanitize']);
jaggedsoft

それは、材料のMD-リストspan要素にブートストラップのアイコンを結合時に、あまりにも私のために働いた
changtung

1

attrを介してhtmlをバインドする以下のコードを試してください

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

このelement.html(scope.dynamic);を試してください element.html(attr.dynamic);より

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.