Data binding in Angular js on line added via $ sce.trustAsHtml

I am implementing a web interface for a legacy system, so the data from the server is fixed. This server data defines various controls that should be displayed to the user, for example. combo boxes, buttons, etc. I parsed the server data and added HTML for the controls via $ sce.trustAsHtml ().

The problem is that the controls are not bound to the model. And if I put an ng-change event handler, it is not called with user rights.

I read this helpful post: Call function inside line $ sce.trustAsHtml () in Angular js which states:

ng-bind-html just inserts plain old html and don't document it (so any directives in html won't be handled by angular.

So this seems to be my problem. But I don't understand Angular well enough to understand the solution suggested by the above post.

I am displaying dynamic text in the following HTML

<span ng-repeat="i in range(0, item.Data.DisplayText.JSArray.length-1)">
  <span ng-bind-html="item.Data.DisplayText.JSArray[i] | trusted_html"></span>
  <span ng-show="!$last"><br></span>
</span>

      

Here is the definition for range ():

$scope.range = function(min, max){
  var input = [];
  for (var i=min; i<=max; i++) input.push(i);
  return input;
};

      

And here is the definition of the filter:

app.filter('trusted_html', ['$sce', function($sce){
    return function(text) {
        return $sce.trustAsHtml(text);
    };
}]);

      

I had help setting up parts of this app and now you can get post solutions from. I am sorry that I cannot provide links to these original solutions.

I think what I need to do is extend the trusted_html filter so that it compiles the text. But I don't know if this will compile the whole DOM (potentially slow) or just a text parameter.

Any help would be appreciated.


ADDITION:

I appreciate @ Simon Cheeseman's answer. He pointed out that the directive would be better than the filter, as I wanted. I am researching the documentation and reading posts and it seems he is 100% correct. DOM manipulation must be done in directives. However, I don't like the idea of ​​having one directive (like the "compile-template" from the above example in the link), fix short circuits of another directive (ng-bind-html). This strikes me as bad form. How do I know the ng-bind-html directive will be executed before the compilation template directive is generated? And what happens if the order is reversed?

So I'm going to consider if they can be combined into one function. I will listen here. If anyone sees a problem please let me know.

I first looked at the ng-bind-html directive from angular.js found here: https://code.angularjs.org/1.2.9/angular.js and in that file, I was looking for "ngBindHTML".

var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) {
  return function(scope, element, attr) {
    element.addClass('ng-binding').data('$binding', attr.ngBindHtml);

    var parsed = $parse(attr.ngBindHtml);
    function getStringValue() { return (parsed(scope) || '').toString(); }

    scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) {
      element.html($sce.getTrustedHtml(parsed(scope)) || '');
    });
 };

      

}];

The above does not apply to the app.directive () form and does not return a {link: fun ()} object. So I'm not entirely sure if this applies directly to the comparison to the compilation pattern directive below. However, on line 2025, I found the following. So I think I am on the right track:

ngBindHtml: ngBindHtmlDirective,

      

So this ngBindHtmlDirective somehow acts like ngBindHtml.

I'm going to take apart the code above and refactor it. And FYI, I'm relatively new to javascript, and anonymous functions and closures are still a little new to me. So I am trying to remove the anonymous functions for my clarity. I'll add some comments from the information I got on the Angular website, etc.

var DirectiveFnFactory = function($sce, $parse) {

  var DirectiveHandlerFn = function(scope, element, attr) {
    //Input:  scope = an Angular scope object 
    //        element = the jqLite-wrapped element that this directive matches
    //        attr = a hash object with key-value pairs of normalized attribute 
    //               names and their corresponding attribute values
    //closure scope input: $compile object
    //                     $parse object
    //Result: none

    element.addClass('ng-binding');
    // .addClass is jQuery:  http://api.jquery.com/addclass/
    //  adds class to element
    element.data('$binding', attr.ngBindHtml);
    //  adds value to key of '$binding';

    var parsed = $parse(attr.ngBindHtml); 
    //Input:  attr.ngBindHtml should be the HTML to be bound.
    //Result:  $parse() returns a function which represents the 
    //         compiled input expression.
    //  This function will have this signature: 
    //     function(context, locals)    
    //            context – {object} – an object against which any expressions 
    //                                 embedded in the strings are evaluated against 
    //                                 (typically a scope object).
    //            locals – {object=} – local variables context object, useful for 
    //                                 overriding values in context.
    //     The returned function also has the following properties:
    //       literal – {boolean} – whether the expression top-level node is a 
    //                             JavaScript literal.
    //                           constant – {boolean} – whether the expression is made 
    //                             entirely of JavaScript constant literals.
    //        assign – {?function(context, value)} – if the expression is assignable, 
    //                             this will be set to a function to change its value 
    //                             on the given context.

    function getStringValue() { return (parsed(scope) || '').toString(); }
    //First, this is executing the function that was returned by parsed(), 
    //    passing scope that was given to DirectiveHandlerFn()
    //Next converting Fn() output to string. 
    //In the case of binding HTML. I would think that the output of this would be HTML

    var watchListener = function ngBindHtmlWatchAction(value) {
      element.html($sce.getTrustedHtml(parsed(scope)) || '');
      //.html is jquery: http://api.jquery.com/html/
      //  Get the HTML contents of the first element in the set of matched elements
      //    or set the HTML contents of every matched element.
    }

    scope.$watch(getStringValue, watchListener);
    //$watch signature: 
    //  $watch(watchExpression, listener, [objectEquality]);
    //  This registers a listener callback to be executed whenever the 
    //    watchExpression()  changes
    //  The listener() has this signature (determined by example, not API documents!):
    //     listener(newValue, oldValue);
    //  The listener is called automagically by Angular when the value changes.
  }

  return DirectiveHandlerFn;
}

var ngBindHtmlDirective = ['$sce', '$parse', DirectiveFnFactory];

      

OK, now I'll first give the compileTemplate directive (here: call function inside $ sce.trustAsHtml () string in Angular js ):

.directive('compileTemplate', function($compile, $parse){
    return {
        link: function(scope, element, attar){
            var parsed = $parse(attr.ngBindHtml);
            function getStringValue() { return (parsed(scope) || '').toString(); }

            //Recompile if the template changes
            scope.$watch(getStringValue, function() {
                $compile(element, null, -9999)(scope);  
               //The -9999 makes it skip directives so that we 
               //do not recompile ourselves
            });
        }         
    }
});

      

And now highlight it to comment:

var DirectiveObjFactory = function($compile, $parse){
    //input: $compile object
    //       $parse object

    var DirectiveHandlerFn = function(scope, element, attr) {
        //Input:  scope = an Angular scope object 
        //        element = the jqLite-wrapped element that this directive matches
        //        attr = a hash object with key-value pairs of normalized attribute 
        //               names and their corresponding attribute values
        //closure scope vars: $compile object
        //                    $parse object
        //Result: none

        var parsed = $parse(attr.ngBindHtml);
        //Input:  attr.ngBindHtml should be the HTML to be bound.
        //Result:  $parse() returns a function which represents the 
        //         compiled input expression.
        //  This resulted function will have this signature: 
        //     function(context, locals)    
        //            context – {object} – an object against which any expressions 
        //                             embedded in the strings are evaluated against 
        //                             (typically a scope object).
        //            locals – {object=} – local variables context object, useful for 
        //                             overriding values in context.
        //     The returned function also has the following properties:
        //       literal – {boolean} – whether the expression top-level node is a 
        //                         JavaScript literal.
        //       constant – {boolean} – whether the expression is made 
        //                             entirely of JavaScript constant literals.
        //        assign – {?function(context, value)} – if the expr is assignable, 
        //                          this will be set to a function to change its value 
        //                             on the given context.

       function getStringValue() { return (parsed(scope) || '').toString(); }
        //First, this is executing the function that was returned by parsed(), 
        //    passing scope that was given to DirectiveHandlerFn()
        //Next converting Fn() output to string. 
        //In the case of binding HTML. I would think that the output of this 
        //  would be HTML

        var watchListener = function ngBindHtmlWatchAction(value) {
          //Input: value -- actual the newValue. (oldValue not accepted here)
          //Locally scoped vars used -- element, scope

          // -- Adding Below is from ngbindHtml ------------
          element.html($sce.getTrustedHtml(parsed(scope)) || '');
          //.html is jquery: http://api.jquery.com/html/
          //Gets the HTML contents of the first element in the set of matched 
          //    elements or set the HTML contents of every matched element.
          // -- End addition  ------------

          var compFn = $compile(element, null, -9999);
          //NOTE: I can't find formal documentation for the parameters for $compile()
          //      below is from examples found...
          //Input: element -- the HTML element to compile
          //       transcludeFunction -- null here.
          //       maxPriority -- "The -9999 makes it skip directives so that we 
          //                       do not recompile ourselves"
          //$compile() compiles an HTML string or DOM into a template and 
          //    produces a template function, which can then be used to link scope 
          //    and the template together.
          //  The returned function accepts a scope variable, against which the code
          //    is evaluated. 
          compFn(scope); // execute the returned function, passing scope  
        } // end watchListener

        scope.$watch(getStringValue, watchListener);
        //$watch() function signature: 
        //  $watch(watchExpression, listener, [objectEquality]);
        //  This registers a listener callback to be executed whenever the 
        //    watchExpression changes
        //  The supplied listener() should have this signature:
        //     listener(newValue, oldValue);
        //  The listener is called automagically by Angular when the value changes.

    } // end DirectiveHandlerFn

    return {link: DirectiveHandlerFn}         

} // end DirectiveObjFactory

app.directive('compileTemplate', DirectiveObjFactory);

      

I think I am almost there. Let me try to put it all together ...

.directive('bindAndWatchHtml', ['$sce', function($compile, $parse){
    return {
        link: function(scope, element, attr){
            var parsed = $parse(attr.ngBindHtml);
            function getStringValue() { return (parsed(scope) || '').toString(); }

            //Recompile if the template changes
            scope.$watch(getStringValue, function() {
                element.html($sce.getTrustedHtml(parsed(scope)) || '');
                $compile(element, null, -9999)(scope);  
                //The -9999 makes it skip directives so that we do not recompile ourselves
            });
        }         
    }
}]);

      

Hopefully this will link and compile the html and trust the HTML at the same time.

Now for testing ...

ADDITION:

Doesn't work .. On the line:

element.html($sce.getTrustedHtml(parsed(scope)) || '');

      

it complains that $ sce is not defined.

...

I have changed the following line and this allows $ sce to be defined.

app.directive('bindAndWatchTrustedHtml0', ['$compile', '$parse', '$sce',
                                       function($compile, $parse, $sce){ ...

      

Next, I get a message about trying to use protected text in an insecure location ....

....

It takes too long. I refuse this. I used this as per the original link at the very top and it works.

<span ng-bind-html="item.Data.DisplayText.JSArray[i] | trusted_html" compile-template>
</span>

      

+3


source to share


1 answer


See https://code.angularjs.org/1.2.19/docs/api/ng/service/ $ compile to compile.

Basically you are calling compiledHtml = $compile('<div>' + text + '</div>')($scope).html()

.



I think you'd be better off creating a directive than using a filter, although you will need a variable $scope

to compile.

Perhaps you could use ngBindTemplate or ngInclude

+1


source







All Articles