Accessing image data after the ngSrc gets resolved with an AngularJS directive - angularjs

I'm trying to write an angularjs directive to process EXIF metadata in an img that is already loaded.
My desired usage:
<img ng-src="{{url}}" my-exif-directive />
My directive looks pretty basic:
...
restrict: 'A',
link: function(scope, element) {
var exifData = EXIF.readFromBinaryFile(/* ... */);
rotate(parseInt(exifData.Orientation || 1, 10), element);
}
...
Is there any way to tap into the image buffer?
When I debug it the ng-src parameter is still not resolved. Is this possible?

Depending on other directives (e.g. ng-repeat), attributes and scope properties may not be interpolated in link. The one can also notice that element.attr('attribute-name') may not be interpolated, while attrs.attributeName will.
Generally it is safe to do this:
link: function(scope, element) {
$timeout(function () {
...
});
}
If other directives don't use timeouts the similar way, directive's DOM is already there, as well as interpolated values.
Since there are bindings involved, and src may change with time, the proper way to handle it is:
link: function(scope, element, attrs) {
attrs.$observe('src', function (attr) {
if (!attr) return;
...
});
}

Related

Validation isn't triggered though attributes are set

My directive looks as follows:
directive('setAttribute', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function ($scope, element, attrs, ctrl) {
var prop = $scope.$eval(attrs.setAttribute);
prop.validationRulesToApply.forEach(function (rule) {
attrs.$set(rule.name, rule.val);
});
}
}
});
As you can this one is for setting attributes dynamically. In spite of attributes are set properly(i can see them in final HTML) no validation is triggered. When i output $error object with curly braces - it is empty! Do i miss something important when setting attributes?
Are these validation rules you are trying to add in setAttribute directive?
If it so, you are doing in wrong way. When you use $set, it just adds attribute to HTMl, but doesnot compile them.
Hence, you won't get results as you are seeking.
You need to add it to pre compile
I think this solution may help you.
Add directives from directive in AngularJS

simple directive to show static text

Our app is not using angular 1.3 (yet, we have to check the dependencies before updating), but I need to use One-time binding from 1.3 in some simple text attributes.
Wrote this directive to accomplish that
return {
scope: {
'text': '='
},
restrict: 'AE',
template: '{{ text }}',
link: function link($scope, element, attrs) {
}
};
And it is used like this
<span static-text text="friend.name">
The problem is that it still adds a watch on {{ text }} (screenshot from Batarang)
Is there a simple way of displaying a text without the permanent watch? (looked at this solution but seems to be too much just for showing some text).
EDIT: I ended up using the solutions proposed by #arturgrzesiak and #PSL, #arturgrzesiak's solution was used when no async proccesing was present, and for the other scenarios I used #PSL's. Both solutions work, but I'll accept #PSL's since it covers more scenarios.
There are some advantages that you get by having a watch. One example is in your actual code you are setting the data asynchronously which means the bound variable gets updated during the next digest cycle. But it's overkill (So bindonce or other watch removal libraries or 1.3 two-way binding exist) in some case. Here is one thing you can do, just use a watch until you get the data and then remove it once you have got it and set the html manually from the directive.
return {
restrict: 'AE',
link: function link($scope, element, attrs) {
var unwatch = $scope.$watch(attrs.staticText, function(val){ //Set up temp watch
if(val){
unwatch(); //Unwatch it
element.html(val); //Set the value
}
});
}
};
and just use it as
<span static-text="friend.name">
The solution is a bit more convoluted than what I proposed in the comment.
app.directive('once', function($parse){
return function(scope, element, attrs){
var parsed = $parse(attrs.once)(scope);
element.html(parsed);
}
});
DEMO

How to prevent duplicated attributes in angular directive when replace=true

I've found that angular directives that specify replace: true will copy attributes from the directive usage into the output rendered by the template. If the template contains the same attribute, both the template attribute value and the directive attribute value will be combined together in the final output.
Directive usage:
<foo bar="one" baz="two"></foo>
Directive:
.directive('foo', function() {
return {
restrict: 'E',
replace: true,
template: '<div bar="{{bar}}" baz="baz"></div>',
scope: {
bar: '#'
},
link: function(scope, element, attrs, parentCtrl) {
scope.bar = scope.bar || 'bar';
}
};
})
Output:
<div bar="one " baz="two baz" class="ng-isolate-scope"></div>
The space in bar="one " is causing problems, as is multiple values in baz. Is there a way to alter this behavior? I realized I could use non-conflicting attributes in my directive and have both the template attributes and the non-conflicting attributes in the output. But I'd like to be able to use the same attribute names, and control the output of the template better.
I suppose I could use a link method with element.removeAttr() and element.attr(). It just seems like there should be a better solution.
Lastly, I realize there is talk of deprecating remove: true, but there are valid reasons for keeping it. In my case, I need it for directives that generate SVG tags using transclusion. See here for details:
https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb
No, there isn't some nice declarative way to tell Angular how x attribute should be merged or manipulated when transplanted into templates.
Angular actually does a straight copy of attributes from the source to the destination element (with a few exceptions) and merges attribute values. You can see this behaviour in the mergeTemplateAttributes function of the Angular compiler.
Since you can't change that behaviour, you can get some control over attributes and their values with the compile or link properties of the directive definition. It most likely makes more sense for you to do attribute manipulation in the compile phase rather than the link phase, since you want these attributes to be "ready" by the time any link functions run.
You can do something like this:
.directive('foo', function() {
return {
// ..
compile: compile
// ..
};
function compile(tElement, tAttrs) {
// destination element you want to manipulate attrs on
var destEl = tElement.find(...);
angular.forEach(tAttrs, function (value, key) {
manipulateAttr(tElement, destEl, key);
})
var postLinkFn = function(scope, element, attrs) {
// your link function
// ...
}
return postLinkFn;
}
function manipulateAttr(src, dest, attrName) {
// do your manipulation
// ...
}
})
It would be helpful to know how you expect the values to be merged. Does the template take priority, the element, or is some kind of merge needed?
Lacking that I can only make an assumption, the below code assumes you want to remove attributes from the template that exist on the element.
.directive('foo', function() {
return {
restrict: 'E',
replace: true,
template: function(element, attrs) {
var template = '<div bar="{{bar}}" baz="baz"></div>';
template = angular.element(template);
Object.keys(attrs.$attr).forEach(function(attr) {\
// Remove all attributes on the element from the template before returning it.
template.removeAttr(attrs.$attr[attr]);
});
return template;
},
scope: {
bar: '#'
}
};
})

Passing all arbitrary attributes from html definition to a template element

I'm making a number of form directives with angular to reuse and I'd like to pass all attributes put on my directive to the input tag in my template.
So if they wrote:
<jays-input required class="well"></jays-input>
Even though my directive doesn't formally take the attributes required or class, it would transfer them to the "input" part of my template. So my template would be something like:
<input placeholder="jays input" {{$all_attributes}} />
...Where $all_attributes would be 'required class="well"'. Even better would be just the attributes that don't match what the directive expects.
While writing this I realized I could just parse the $attrs array my self, but I wonder if there is a shorthand or something. I feel like it would be common to make a directive that merely wraps html around a specific element and in that case you'd want to transfer all attributes to the focal element.
In your directive definition's compile function, add the attributes using jQuery:
app.directive('jaysInput', function() {
return {
restrict: 'E',
compile: function(element, attr) {
$('input', element).each(function(){
for (var i in attr)
{
$(this).attr(i, attr[i]);
}
}
return function(scope, element, attr) {
// return link function
};
}
}
});

Obtaining element attributes within a custom directive used within ng-repeat

This is driving me a little bit crazy. I need to read the src of an image within a custom attribute:
app.directive('imgTransform', function () {
return {
retrict: 'A',
link: function (scope, elem, attrs) {
console.log(elem.attr('ng-src'));
}
}
});
This works fine when used like so:
<img ng-src='http://lorempixel.com/100/100/technics' alt="" img-transform="" />
However, it does not work inside ng-repeat:
<p ng-repeat='image in images'>
<img ng-src='{{image}}' alt="" img-transform="" />
</p>
The value returned is {{image}}. How do I get the actual value?
Try using the attrs:
console.log(attrs.ngSrc);
Fiddle: http://jsfiddle.net/6SuWD/
The reason for this could be that ng-repeat uses the original DOM as template and recreates it for each iteration. For some (obscure to me) reason, you are reading the attribute of the template. This explanation could be very wrong though...
However, since Angular gives you the API to access the attributes, it would be safer to go with it anyway.
You will have to watch for changes in this attribute using $observe since ng-repeat interpolates the values of ng-src. See this reference.
LukaszBachman is correct. When you pass interpolated values to a directive, the interpolation hasn't fired yet when the directive is in its linking phase.
If you were to do console.log(attrs); - you would clearly see that there is an actual value on ngSrc, when looking in the browser console. However, since interpolation hasn't kicked in yet you dont have access to it.
This would get you the actual value of ngSrc:
myApp.directive('imgTransform', function () {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
attrs.$observe('ngSrc', function (val) {
console.log(val);
});
}
}
});

Resources