1

I have html content that should be output by ng-bind-html directive, and after I would like to do some manipulations with this content(for example DOM manipulations, jQuery plugins, etc).

stackoverflow provides me such solution:

<div ng-app="myApp" ng-controller="myCtrl">
   <div ng-bind="sometext" my-directive>before</div>
</div>

so to create custom directive with higher priority and watch inside:

angular.module('myApp').directive('myDirective', function() { 
    return {
        priority: 10, 
        link: function(scope,element,attrs) {
            scope.$watch(attrs.ngBind, function(newvalue) {
              console.log("element ",element.text());
            });           
        }
    };      
 });

and the Demo

but as far as I'm not going to change this content I don't want to use $watch. Is it possible to do without $watch?

Community
  • 1
  • 1
Stepan Suvorov
  • 25,118
  • 26
  • 108
  • 176
  • 1
    This doesn't make any sense to me. You are storing HTML in your model that you want some 3rd party plugin to manipulate? Something feels wrong here, and seems counter to the goals of angular. – Claies Jul 14 '15 at 14:55
  • How are you setting the value of your `ng-bind-html`? through ajax or using some action? – Pankaj Parkar Jul 14 '15 at 15:15
  • @Claies, we are storing HTML in DB and, so yes - in the model, via ajax (yes @Pankaj) – Stepan Suvorov Jul 14 '15 at 18:24
  • 1
    honestly the practice of storing HTML in the Database makes no sense. – Claies Jul 14 '15 at 20:51
  • why so? I have content that is prepared in WYSIWYG editor with possibilities to paste whatever you want there (like image, video). – Stepan Suvorov Jul 15 '15 at 08:52

2 Answers2

1

Option 1 (avoid $watch as requested):

One option would be to skip ng-bind-html and $compile or $parse your html yourself. Angular's ngBindHtml itself does something along the lines of:

var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml);
// ... It does some other, less relevant stuff between these
element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');

Angular's relevant source code can be viewed here

So you could always write a custom directive that does those things, along with the post-processing (even pre-processing, if required). Something along the lines of:

var ngBindHtmlGetter = $parse(attrs.someAttrContainingContent);
element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || '');
doPostProcessing(element); // Your additional stuff goes here

Option 2 (keep $watch, but unbind):

Another option, which may be simpler for you, if you want to use ng-bind-html and you're just worried about having the extra watcher around, is to just unbind the watcher. You can unbind it quite easily ($watch returns an "unbind" function):

var unbindPostProcess = scope.$watch(attrs.ngBind, function(newvalue) {
    doPostProcessing(element); // Whatever the additional function may be
    unbindPostProcess(); // Perhaps guarded to unbind at the right time.
});
DRobinson
  • 4,441
  • 22
  • 31
  • I feel like in first option we still need watch, could your provide fiddle for it please? – Stepan Suvorov Jul 15 '15 at 08:56
  • Right, there will be a watcher. `ngBind` and `ngBindHtml` themselves add a watcher, and you were suggesting using that, so I assumed you were trying to avoid _additional_ watchers (i.e. from your directive). If you want to avoid them in your code, you can do some acrobatics: `
    `, now at link time you'll have the HTML content and need no further watchers (*note*: `ngIf` adds a watcher itself). Here's an example of that: http://jsfiddle.net/cnU4Z/12/
    – DRobinson Jul 15 '15 at 12:48
  • If you want no watchers at all, I'd suggest combining my Option 1 with my Option 2. As in, use Option 1 to build the HTML, Option 2 to kill the one watcher - using any internal Angular directive will add watchers. All said: **I don't truly suggest any of this**, and don't think avoiding watchers at all costs is a good thing. Watchers are part of what makes Angular useful in the first place. – DRobinson Jul 15 '15 at 12:50
  • Thank you for your detailed answers! I pretty understand what the watchers are, even made presentation about them - https://youtu.be/UjnL8w9XRvk?t=268 :) Just trying to think out of box. and thank you for very interesting variant with `ng-if`! – Stepan Suvorov Jul 15 '15 at 18:24
  • Right, I wasn't doubting that you know what they are. If you're really worried about too many watchers, you might actually want to use them more - If you add the watcher, then Angular isn't doing it for you (`ngIf` adds a watcher, `ngBind` adds one, etc.), and if you add the watcher yourself, then you can also unbind it (outlined in Option 2 of above post), leaving you with 0 watchers. With `ng-if`, you'll always have one: http://jsfiddle.net/cnU4Z/13/ – DRobinson Jul 15 '15 at 18:28
0

I might be misunderstanding what you're looking for completely but, if the question is;

How do I bind HTML to the view without a $watcher?

this jsBin showcases how.

By upgrading to angular-1.3.x you get access to one time bindings {{::expr}}.

So with that, it's really just a matter of trusting whatever sometext may be as HTML (by using $sce) and outputting it using ng-bind-html="::sometext".

Et voila, no $watchers and you can scrap the custom directive entirely.


Oh, and if this is not what you are after, do tell and I will delete my answer.

  • yes, unfortunately it's not what I'm looking for. my question is more about post-processing. – Stepan Suvorov Jul 14 '15 at 18:26
  • Care to elaborate on that? I mean, what does the HTML content that you want exposed look like? Is the HTML the carrier of a directive you want to run? I'm having a hard time wrapping my head around what the end goal is here. –  Jul 14 '15 at 18:33
  • I have content that is created by WYSIWYG editor. For example for images I need to apply jquery fancybox and for iframe (that are embedded video) I create wrapper to make them responsive on mobile handsets. – Stepan Suvorov Jul 15 '15 at 08:45