1. ホーム
  2. html

[解決済み] データベースから動的な HTML 文字列をコンパイルする

2022-07-10 01:50:03

質問

状況

Angular アプリの中には、Page というディレクティブがあり、コントローラによってバックアップされ、ng-bind-html-unsafe 属性を持つ div が含まれています。これは '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>

Pageコントローラに戻って、対応する$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を誤解しているのか、単純な何かを見逃しているのか、あるいは私が遅いのかわかりません。いずれにせよ、私はいくつかのアドバイスを得ることができました。

どのように解決するのですか?

ng-bind-html-unsafe は、コンテンツをHTMLとしてレンダリングするだけです。これはAngularのスコープを結果的にDOMにバインドするものではありません。そのため $compile サービスを使う必要があります。私が作った このプランカー を使用する方法を示すために $compile を使用して、ユーザが入力した動的な HTML をレンダリングし、コントローラのスコープにバインドするディレクティブを作成する方法を示します。ソースは以下に掲載します。

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