Override or deactivate built-in directive ng-src

3,017 views
Skip to first unread message

Jacob Rief

unread,
Feb 2, 2013, 6:44:08 PM2/2/13
to ang...@googlegroups.com
Is there a way to override or to deactivate a built-in directive, such as ng-src?

In my concrete situation, I added some functionality to a customized ng-src directive, which loads high resolution images in browsers running on a Retina display. Sure, I could do something like <img myapp-src="/path/to/image.png"> but this means that I can not use the intended built-in directive ng-src and additionally I have to rewrite all the code using ng-src. If I add a directive named ngSrc to my own app's module, the code for ng-src in angularjs is executed as well, loading the image twice, once with the normal resolution and once in high resolution, causing unnecessary bandwidth consumption and loading time. Therefore I would like to simply replace/deactivate that part of the current angularjs code with my own.

Other idea: Since this feature can be useful to many other users, I could implement it directly into angularjs and create a pull request.

BTW, I was inspired by the idea of rewriting the <img src=...> using this code: http://retinajs.com/, but having this same feature in angularjs, would make the retinajs module obsolete, even for hard coded image URLs.

Jacob

Florian Orben

unread,
Feb 2, 2013, 8:43:14 PM2/2/13
to ang...@googlegroups.com
Hey,

as far as I know, there is no way to override built-in directives, but depending on how you're going to load retina/non-retina versions of the image, you could probably just write a filter, like this:

- Flo

cyberflohr

unread,
Feb 3, 2013, 5:39:32 AM2/3/13
to ang...@googlegroups.com
Hi,

Just declare your own ngSrc directive in your module, this will override the original angularjs directive. I already did this for the ngClick directive on mobile devices, an replaced ngClick with the "google fast button" implementation for faster click handler execution.

- wolf

Florian Orben

unread,
Feb 3, 2013, 7:51:44 AM2/3/13
to ang...@googlegroups.com
Hey Wolf,

I don't think simply declaring a new ngSrc directive would work, since you can declare multiple directives with the same name = attach multiple directives to one element, a good example is the already inbuilt input directive.
For example see here: http://plnkr.co/edit/zj5B9WMJLVepVWSvRPSo?p=preview , that's 3 new + the original ngSrc directive applied, and all are executed.

- Flo
Message has been deleted
Message has been deleted

cyberflohr

unread,
Feb 3, 2013, 11:42:33 AM2/3/13
to ang...@googlegroups.com
Hi Flo,

I used the following "trick" - mark the directive as terminal = true and use priority = 100. With this trick you can override the angular built-in directive. BUT ... the drawback is, that only directives with a priority greater >= 100 are executed for this element. 

app.directive('ngSrc', function(){
  return {
    restrict : 'A',
terminal: true,
priority : 100,
    link: function (scope, elem, attr) {
      console.log('first ngSrc directive');
    }
  };
});

-wolf

Florian Orben

unread,
Feb 3, 2013, 12:01:32 PM2/3/13
to ang...@googlegroups.com
Ah, neat trick :)

Jacob Rief

unread,
Feb 3, 2013, 4:12:46 PM2/3/13
to ang...@googlegroups.com
Thanks, this trick works!
I didn't know that. I tried with priority=100 because the native ngSrc had priority=99, but that only changed the execution order.

Jacob

Jacob Rief

unread,
Feb 3, 2013, 4:57:35 PM2/3/13
to ang...@googlegroups.com
I just did some more tests, and unfortunately setting terminal: true deactivates all other directives on that element as well. This means that you can not add any ng-click nor ng-class, etc. to an element containing your customized ng-src. One workaround would be to iterate over all elements in attr, and try to re-register them, but that seems to be an ugly hack.

Peter Bacon Darwin

unread,
Feb 3, 2013, 5:10:09 PM2/3/13
to ang...@googlegroups.com
Give this a go!  It is a hack because we should really be playing with $$observers but it works:


Basically all ngSrc is doing is adding an observer to its own attribute.  This removes that observer and adds its own, which in this case does exactly the same thing but adds a prefix.


--
You received this message because you are subscribed to the Google Groups "AngularJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to angular+u...@googlegroups.com.
To post to this group, send email to ang...@googlegroups.com.
Visit this group at http://groups.google.com/group/angular?hl=en-US.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Jacob Rief

unread,
Feb 3, 2013, 5:59:53 PM2/3/13
to ang...@googlegroups.com
Thanks, this does the job!
No more priority and terminal settings required in my directive and all other directives on that element still work.
The only issue which remains, I can not acces the msie variable. 

Florian Orben

unread,
Feb 3, 2013, 6:21:50 PM2/3/13
to ang...@googlegroups.com
msie is only available in angular's internals as far as i know, here's the code used in angular.js:
var msie              = parseInt(((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]), 10);

Peter Bacon Darwin

unread,
Feb 4, 2013, 4:07:14 AM2/4/13
to ang...@googlegroups.com

Yes sorry I did a quick cut and paste

Pete
...from my mobile.

--

cyberflohr

unread,
Feb 4, 2013, 1:50:35 PM2/4/13
to ang...@googlegroups.com
Hi Pete,

Interesting solution with the $$observers - thx!

important note for others: this will only works for expressions!

OK: <img ng-src="{{ 'myImage.jpg' }}"></img>

NOK: <img ng-src="myImage.jpg"></img>

-
wolf

Jacob Rief

unread,
Feb 4, 2013, 4:04:57 PM2/4/13
to ang...@googlegroups.com

On Monday, February 4, 2013 7:50:35 PM UTC+1, cyberflohr wrote:

NOK: <img ng-src="myImage.jpg"></img>


this works for me, and I use it.

cyberflohr

unread,
Feb 4, 2013, 4:48:46 PM2/4/13
to ang...@googlegroups.com
but if your new ngSrc directive will change the image name myimage.jpg to myimage2x.jpg, you will see to network requests, one for myimage.jpg and myimage2x,jpg.

my test program will show this behavior, but if i use an expression i.e, {{'myimage.jpg'}} only one network request will be generated.

cyberflohr

unread,
Feb 4, 2013, 5:03:12 PM2/4/13
to ang...@googlegroups.com
PS: I use chrome on Win7

Jacob Rief

unread,
Feb 5, 2013, 9:07:10 AM2/5/13
to ang...@googlegroups.com
You are absolutely right!
If used with a hard coded URL, first the low-res image is loaded, and, only afterwards the high-res image.
It seems, as if the built-in tag ng-src still has a pending observe event, which is fired before my customized ng-src can disable its observer.
So, up to now I did not find a satisfactory solution to disable the built-in tag ng-src.

I checked this on Safari 6.0.2 on OSX 10.8.

Florian Orben

unread,
Feb 5, 2013, 1:31:00 PM2/5/13
to ang...@googlegroups.com
Have you tried (re-)adding the priority to your custom ngSrc?
It should be at least 100, since angular's standard ngSrc is executing at priority 99 (and would therefor be executed (i.e. adding the observer) before a directive with a priority< 99)

- Flo

Peter Bacon Darwin

unread,
Feb 5, 2013, 3:46:22 PM2/5/13
to ang...@googlegroups.com
The trouble with putting priority to 100 is that you can't then remove the observer because it has not yet been added!

The problem with src being updated if it is not interpolated is that when you register a $observer it also immediately adds one execution of the handler function to the $rootSceop.$$asyncQueue.
You can't really empty the queue as it might contain other important stuff.

Here is an alternative solution that basically removes the value from the ngSrc attribute before the built in ngSrc directive gets a chance to mess with it: http://plnkr.co/edit/V0snzTIzdFsKxa5USKvQ?p=preview


--

Florian Orben

unread,
Feb 5, 2013, 3:55:49 PM2/5/13
to ang...@googlegroups.com
Well spotted! That should have been obvious to me actually - my bad...

Symblify

unread,
Feb 5, 2013, 7:58:29 PM2/5/13
to ang...@googlegroups.com
I noticed in the docs that $provide has a decorator function which seems like it could be used to change the behaviour of a built-in service. After trying a few things out I was able to work out that this is true of directives also. One thing I always wanted to try was to change the way the input directive works so that it changes the model on another event, for example 'blur'.

I've put together a plunk with this particular example: http://plnkr.co/edit/nMruiQdhdSS6B2j1XT8f?p=preview

Seems to work okay. Looks like something similar could help with your ng-src issue.

I'd appreciate comments from the "community experts" on whether this is a good approach as it seems like I've stumbled on something quite useful.

S

Peter Bacon Darwin

unread,
Feb 6, 2013, 4:33:51 AM2/6/13
to ang...@googlegroups.com
@Symblify - you are a genius! I never knew that!

Here is a working example with ngSrc:


Note that you have to overwrite the compile function not the link function.

Pete


--

Jacob Rief

unread,
Feb 6, 2013, 9:06:35 AM2/6/13
to ang...@googlegroups.com
Great! Thank you so much for all the help.

Here is a working version of the fully functional angular-retina.js module
by adding it to any ng-app, all images referenced via <img ng-src="..."> are replaced by their high resolution variant. 

Note, that since plnkr.co does not allow to upload images, I had to fake a HEAD request using a local text file.

Symblify

unread,
Feb 6, 2013, 9:28:27 AM2/6/13
to ang...@googlegroups.com
Thanks, Pete, that means a lot coming from someone as active in the community as yourself.

The thing about the compile function was one of the gotchas I found, but just by checking the Angular code I was able to see that if there is a link function but no compile function, it sets the compile function to be the link function. So changing the link function has no effect. The other thing is that when using this with directives, the $delegate is an array. This actually ties up with what was said earlier in the thread about adding a second "ng-src" causing the original and new directives to run - Angular must allow more than one directive with the same name, hence an array of directives is returned.

My feeling is that the decorator technique could open up a lot of possibilities. Most things in Angular are injectable, right? So any of these could be decorated, potentially.

S

Peter Bacon Darwin

unread,
Feb 6, 2013, 9:48:24 AM2/6/13
to ang...@googlegroups.com
Yes you are right about $decorators opening up many opportunities.  Nice work.
By the way, the compile function is supposed to return the link function.  Putting a link function in a directive definition object is really just a short cut for an empty(ish) compile function:

link: myLinkFunction

is equivalent to

compile: function() {
  return myLinkFunction;
}

It just happens that the compiler just references the actual link function before you get to the decorator if it is provided so it doesn't notice if you change directive.link.

Jacob Rief

unread,
Feb 6, 2013, 11:41:39 AM2/6/13
to ang...@googlegroups.com
Actually the previous version did not work in some edge cases.
I had a partial using the ng-src directive and there, the variable in <img ng-src="{{variable}}"> was not correctly expanded into its value.
To be concrete attrs.$observe('ngSrc', function(value){...} got the string '{{variable}}' and not the expanded value of that variable.

I now mixed the compile and link step. The compile function deactivates the built-in observer and the link function sets a modified observer.
This code http://plnkr.co/edit/cus7nM13smzOoSckf673?p=preview now handles variable expansion and does not preload the image in low-resolution.

Jacob

Robin Poltronieri

unread,
Sep 7, 2013, 2:26:26 AM9/7/13
to ang...@googlegroups.com
This is a relatively old post so I hope someone is still listening...

Trying to solve a similar problem of mine with select directive i found this useful thread. The solution of Simblify is very effective and clean and probably the best one. I'm wondering if it is possibile to know who is requesting the service instance (in the cas of directives, which tag, with which attributes', is being compiled); knowing the 'caller' it would be possible to take different actions.
In example, I want do 'remove' original select directive only when applying the selectmenu jQuery plugin:

   <select ng:model="..." ng:options="..." ></select>          <- leave this as is
   <custom:dropdown ng:model="...">                               <-take some actions when compiling this one
   ...
   </custom:dropdown>

 the custom directive <custom:dropdown> has a template similar to this one:
   <select class="ui_base-dropdown-menu" ng:transclude></select>

So, only when compiling <custom:dropdown> I should 'remove' original select directive.

Anyone have an idea?
Thanks.
Reply all
Reply to author
Forward
0 new messages