Компиляция динамических строк HTML из базы данных

132

Ситуация

В наше приложение Angular вложена директива с именем Page, поддерживаемая контроллером, которая содержит div с атрибутом ng-bind-html-unsafe. Это назначается переменной $ scope с именем pageContent. Этой переменной назначается динамически генерируемый HTML из базы данных. Когда пользователь переходит на следующую страницу, вызывается БД, и для переменной 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
                    }
                }
            }
        };
    });

Шаблон директивы страницы ("page.html" из свойства templateUrl выше)

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

});

Это работает. Мы видим, что HTML-код страницы из БД красиво отображается в браузере. Когда пользователь переходит на следующую страницу, мы видим содержимое следующей страницы и так далее. Все идет нормально.

Эта проблема

Проблема здесь в том, что мы хотим иметь интерактивный контент внутри контента страницы. Например, HTML может содержать миниатюру изображения, где, когда пользователь щелкает по нему, Angular должен сделать что-то удивительное, например, отобразить всплывающее модальное окно. Я разместил вызовы методов Angular (ng-click) в строках HTML в нашей базе данных, но, конечно, Angular не будет распознавать ни вызовы методов, ни директивы, если он каким-то образом не анализирует строку HTML, распознает их и компилирует их.

В нашей БД

Контент для страницы 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 );
}

Я не могу понять, как вызвать этот метод doSomethingAwesome из строки HTML из БД. Я понимаю, что Angular каким-то образом должен анализировать строку HTML, но как? Я читал невнятное бормотание о службе компиляции $, скопировал и вставил несколько примеров, но ничего не работает. Кроме того, в большинстве примеров показано, что динамический контент устанавливается только на этапе связывания директивы. Мы бы хотели, чтобы Пейдж оставался в живых на протяжении всего времени существования приложения. Он постоянно получает, компилирует и отображает новый контент, когда пользователь листает страницы.

В абстрактном смысле, я думаю, вы могли бы сказать, что мы пытаемся динамически вкладывать фрагменты Angular в приложение Angular, и нам нужно иметь возможность менять их местами.

Я несколько раз читал различные фрагменты документации по Angular, а также всевозможные сообщения в блогах и JS возился с человеческим кодом. Я не знаю, полностью ли я неправильно понимаю Angular, или просто упускаю что-то простое, или, может быть, я медленный. В любом случае я мог бы воспользоваться некоторыми советами.

giraffe_sense
источник
2
$ compile и связанные с ним блоги docs заставляют меня чувствовать, что я тоже медленный - хотя я чувствую, что мои js довольно сильны - я думаю, что если я справлюсь с этим, я сделаю блог в стиле идиота - это моя специальность!
приземлился

Ответы:

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
Большое спасибо, Буу! Создание директивы атрибута и добавление функции наблюдения за областью видимости - вот две вещи, которых мне не хватало. Теперь, когда это работает, думаю, я еще раз прочитаю директивы и $ compile, чтобы лучше понять, что происходит под капотом.
giraffe_sense
11
Я тоже! Команда Angular действительно могла бы улучшить документацию по этому поводу.
Craig Morgan
$compile(ele.contents())(scope);- эта строка решила мою проблему не компилировать угловые компоненты, которые добавляются динамически. Спасибо.
Миталь Притмани
@BuuNguyen внутри teplateURL предположим, что если вы включаете какую-то динамическую страницу htmnl с помощью ng-bind-html, тогда использование компиляции не работает, дает ошибку из-за некоторого небезопасного контента с другой стороны с помощью trustAsHTml только удаляет небезопасную ошибку, не компилируется, какие-либо предложения?
анам
1
Мне нравится этот пример, но он не помогает мне работать. У меня есть оператор переключения, который происходит из-за выбора пользователя, поэтому он динамический. В зависимости от этого я хочу вставить html, содержащий директиву. Директива работает, если я помещаю ее в естественную фазу начальной загрузки. Однако у меня это просто не срабатывает --- case 'info': $ scope.htmlString = $ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>'); перерыв; --- когда я хочу сделать что-то вроде --- $ compile ($ sce.trustAsHtml ('<div dynamic = "htmlString"> dddzzz </div>')); Любые идеи о обходных путях и т.де ...
приземлились
19

В angular 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: невозможно вызвать метод insertBefore со значением null. Итак, после некоторого поиска в Google я обнаружил, что должен использовать element.append. Но если я использую эту директиву в нескольких местах, она генерирует многократный HTML. Таким образом, 2 директивы генерируют 4 одинаковых HTML-кода. Спасибо за Ваш ответ.
DzeryCZ
Я бы не стал использовать append вместо вас, я посмотрю на это сегодня вечером и вернусь к вам. Честно говоря, я без проблем использовал эту директиву в нескольких местах на странице. Я постараюсь воспроизвести проблему и вернусь к вам.
Александрос Спиропулос
1
@AlexandrosSpyropoulos Я просто тестирую и вижу, что мой код работает нормально даже с 1.2.12. Я думаю, вы, наверное, пропустили объявление <div dynamic = "html"> в HTML? (С этим объявлением $ watch наблюдает за свойством 'html' в области видимости, а не за фактическим HTML, как вы упомянули, поэтому ошибки недопустимого символа быть не должно.) Если нет, пришлите мне plunkr, который показывает, что он не работает, я посмотрим что не так.
Буу Нгуен
Возможно ты прав. Тогда я ожидал, что html на самом деле является переменной, содержащей html: P. Тем не менее, неплохо установить область действия ваших директив. umur.io/…
Александрос Спиропулос
$compile(ele.contents())(scope);- эта строка решила мою проблему не компилировать угловые компоненты, которые добавляются динамически. Спасибо.
Миталь Притмани
5

Нашел в группе обсуждения Google. Работает для меня.

var $injector = angular.injector(['ng', 'myApp']);
$injector.invoke(function($rootScope, $compile) {
  $compile(element)($rootScope);
});
kwerle
источник
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 и angular-sanitize.js:var myApp = angular.module('myApp', ['ngSanitize']);
jaggedsoft
это сработало и для меня во время привязки значка начальной загрузки к материальному элементу диапазона md-list
changtung
1

Попробуйте этот код ниже для привязки html через 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);
        });
      }
    };
  });

Попробуйте этот element.html (scope.dynamic); чем element.html (attr.dynamic);

Рамеш М
источник