SoFunction
Updated on 2025-04-04

Analysis of method of dynamic binding HTML in AngularJS

This article describes the method of dynamically binding HTML by AngularJS. Share it for your reference, as follows:

In the development of the web front-end, we often encounter the need to dynamically bind some HTML strings from the back-end or dynamically spliced ​​to the page DOM to display, especially in the content management system (CMS: is the abbreviation of Content Management System). Such needs are everywhere.

For readers of angular, they will definitely think of ngBindHtml first. Yes, angular provides us with this directive to dynamically bind HTML, which will bind the calculated expression results to the DOM using innerHTML. But the problem is not that simple. In Web Security XSS (Cross-site scripting, script injection attack), it is a typical computer security vulnerability in web applications. XSS attack refers to the purpose of the attack by injecting executable client code into the web page and successfully executed by the browser, forming an effective XSS attack. Once the attack is successful, it may obtain some sensitive information from the user, change the user's experience, induce users and other illegal behaviors. Sometimes XSS attacks will also implement other attack methods at the same time, such as SQL injection attack servers and databases, Click hijacking, relative link hijacking, etc., which brings huge harm and is also the number one enemy of web security. For more web security issues, please refer to wiki /wiki/Cross-site_scripting%E3%80%82

In angular, the HTML content added is not trusted by default. For added HTML content, you must first use $ to tell angular that this is trustworthy HTML content. Otherwise you will get an exception error with $sce:unsafe.

Error: [$sce:unsafe] Attempting to use an unsafe value in a safe context.

Here is a demo that binds a simple angular link:

HTML:

<div ng-controller="DemoCtrl as demo">
  <div ng-bind-html=""></div>
</div>

JavaScript:

("", [])
  .controller("DemoCtrl", ["$sce", function($sce) {
    var vm = this;
    var html = '<p>hello <a href="/">angular</a></p>';
     = $(html);
    return vm;
  }]);

For simple static HTML, this problem is solved. But for complex HTML, the complexity here refers to HTML templates with angular expressions and directives. For them, we not only want to bind large DOM displays, but also want to get angular's powerful two-way binding mechanism. ngBindHhtml does not have two-way bindings associated with $scope. If angular instructions such as ngClick, ngHref, ngSHow, ngHide and other angular instructions in HTML are present, they will not be compiled. Clicking these buttons will not happen, and the bound expression will not be updated. For example, try to change the last link to: ng-href="", the link will not be parsed, and what you see in the DOM will still be the same HTML string.

To take effect, all instructions in angular need to go through compile, which includes pre-link and post-link in compile, and connect to specific behaviors before they can work. In most cases, compile will be automatically compiled when angular is started. But if it is for a dynamically added template, you need to compile manually. angular provides us with the $compile service to implement this function. Here is a more general compile example:

HTML:

<body ng-controller="DemoCtrl as demo">
  <dy-compile html="{{}}">
  </dy-compile>
  <button ng-click="();">change</button>
</body>

JavaScript:

("", [])
  .directive("dyCompile", ["$compile", function($compile) {
    return {
      replace: true,
      restrict: 'EA',
      link: function(scope, elm, iAttrs) {
        var DUMMY_SCOPE = {
            $destroy: 
          },
          root = elm,
          childScope,
          destroyChildScope = function() {
            (childScope || DUMMY_SCOPE).$destroy();
          };
        iAttrs.$observe("html", function(html) {
          if (html) {
            destroyChildScope();
            childScope = scope.$new(false);
            var content = $compile(html)(childScope);
            (content);
            root = content;
          }
          scope.$on("$destroy", destroyChildScope);
        });
      }
    };
  }])
  .controller("DemoCtrl", [function() {
    var vm = this;
     = '<h2>hello : <a ng-href="{{}}">angular</a></h2>';
     = '/';
    var i = 0;
     = function() {
       = '<h3>change after : <a ng-href="{{}}">' + (++i) + '</a></h3>';
    };
  }]);

Here is a command called dy-compile. It first listens to the changes in the html value of the binding attribute. When the html content exists, it will try to create a child scope first, and then use the $compile service to dynamically connect the passed html and replace the current DOM node. The reason for creating a child scope here is that it is convenient for each time the DOM is destroyed, it can easily destroy the scope, remove the watchers function brought by HTML compile, and try to destroy the scope when the last parent scope is destroyed.

Because of the above compile and connection, the ngHref directive can take effect. Here is just an example of the dynamic compile angular module. For the specific implementation method, please refer to your business to declare a specific directive.

I hope this article will be helpful to everyone's AngularJS programming.