从数据库编译动态HTML字符串


132

情况

嵌套在我们的Angular应用程序中的是一个称为Page的指令,该指令由控制器支持,该指令包含一个具有ng-bind-html-unsafe属性的div。这被分配给名为“ pageContent”的$ scope变量。从数据库中为该变量分配动态生成的HTML。当用户跳至下一页时,将对数据库进行调用,并将pageContent var设置为此新的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指令的模板(来自上面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;
        });

});

这样可行。我们看到来自数据库的页面HTML在浏览器中呈现得很好。当用户切换到下一页时,我们会看到下一页的内容,依此类推。到目前为止,一切都很好。

问题

这里的问题是我们希望在页面内容中包含交互式内容。例如,HTML可能包含一个缩略图,当用户单击它时,Angular应该做一些很棒的事情,例如显示弹出的模式窗口。我已经在数据库的HTML字符串中放置了Angular方法调用(ng-click),但是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>

回到Page控制器,然后添加相应的$ scope函数:

页面控制器

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

我无法弄清楚如何从数据库的HTML字符串中调用“ doSomethingAwesome”方法。我意识到Angular必须以某种方式解析HTML字符串,但是如何?我已经读过有关$ compile服务的含糊不清的说法,并复制并粘贴了一些示例,但是没有任何效果。同样,大多数示例都显示动态内容仅在指令的链接阶段设置。我们希望Page在应用程序的整个生命周期中保持活力。当用户翻阅页面时,它会不断接收,编译和显示新内容。

从抽象的意义上讲,我想您可以说我们正在尝试在Angular应用程序中动态嵌套Angular的块,并且需要能够将它们交换进出。

我已经多次阅读了Angular文档的各种内容,以及各种博客文章和JS Fiddled的人的代码。我不知道我是完全误解了Angular,还是只是错过了一些简单的东西,或者我很慢。无论如何,我可以使用一些建议。


2
$ compile和围绕它的docs博客使我感到自己也很慢-即使我觉得我的js非常强大-我想如果我能掌握这一点,我会做一个白痴风格的博客-这就是我的专长!
降落

Answers:


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
非常感谢,Buu!创建属性指令和添加范围监视功能是我缺少的两件事。既然可以正常工作了,我想我将再次阅读指令和$ compile,以更好地了解幕后情况。
giraffe_sense

11
我也一样!Angular团队确实可以改善此文档。
Craig Morgan

$compile(ele.contents())(scope);-此行解决了我不编译动态添加的角度分量的问题。谢谢。
Mital Pritmani 2014年

teplateURL中的@BuuNguyen假设如果您使用ng-bind-html包含了一些动态htmnl页面,则使用compile无法正常工作会给某些不安全的内容带来错误,而使用trustAsHTml只会删除不安全的错误而不会进行编译,有什么建议吗?
anam 2014年

1
我喜欢这个例子,但没有使我的工作。我有一个switch语句,由于用户选择而发生,因此它是动态的。取决于我要插入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属性获取属性来解决了这一问题

 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 2014年

我不会在您的位置使用append,今晚我将看一看,我会尽快回复您。老实说,我在页面中的很多地方都使用了此指令,没有任何问题。我将尝试重现该问题,并尽快与您联系。
Alexandros Spyropoulos 2014年

1
@AlexandrosSpyropoulos我只是测试一下,看到我的代码即使在1.2.12下也可以正常运行。我认为您可能错过了HTML中的声明<div dynamic =“ html”>吗?(有了该声明,$ watch监视作用域中的'html'属性,而不是您所提到的实际HTML,因此应该没有无效的char错误。)如果没有,请向我发送表明其无效的plunkr,我会发现出了什么问题。
Buu Nguyen 2014年

你也许是对的。我一直期望那时,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和angular-sanitize.js:var myApp = angular.module('myApp', ['ngSanitize']);
jaggedsoft

在将引导程序图标绑定到材料md-list跨度元素上时对我也
有用

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.