I'm attempting to create a directive to perform scaling on child images. The images are embedded using ng-repeat, with the URLs coming from a REST API call in the parent controller. Here's the directive's embed:
And here's the template for the directive:
<div>
<img image-directive-item ng-src="{{image.url}}" ng-style="{'width': {{image.width}}, 'height': {{image.height}}}" ng-if="image.done" />
</div>
To scale each image, I need to have information re: width/height of other images around it. Currently, I'm attempting to pass this info to the parent on load in a child directive binded to the image. Here's the code I'm testing:
angular.module("myApp")
.directive("imageDirective", function() {
return {
scope: { images: '=images'},
link: function(scope){
for(var i = 0; i < scope.images.length; i++) {
console.log(scope.images[i].width, scope.images[i].height);
}
},
templateUrl: _contextPath + 'static/html/imageDirective.html'
};
})
.directive("imageDirectiveItem", function() {
return {
link: function(scope, element) {
element.bind("load", function(e) {
scope.image.width = this.naturalWidth;
scope.image.height = this.naturalHeight;
});
}
};
});
The images are loading on the page, and the load callback can read the dimensions, however the output in the console (from the parent directive) is "undefined undefined" for all images, and an error about being unable to set the width/height to an empty value.
I want to do it in this format since I need to process the images in the order they appear in the DOM. However, I'm struggling to make the two connect without resorting to what feel like hacks. Any ideas?
Solution provided by New Dev below worked for me. Modified how the element is passed to the parent directive so that it passes the object from the load callback, which contains the information I needed. Learned something new about Angular today!
angular.module("myApp")
.directive("imageDirective", function() {
return {
scope: { images: '=images'},
controller: function($scope){
var toBeLoaded = $scope.images.length;
this.childLoaded = function(childElement) {
console.log(childElement.naturalWidth, childElement.naturalHeight);
if(--toBeLoaded == 0) {
allChildrenLoaded();
}
};
function allChildrenLoaded() {
console.log("All children loaded.");
}
},
templateUrl: _contextPath + 'static/html/imageGrid.html'
};
})
.directive("imageDirectiveItem", function() {
return {
require: "^imageDirective",
link: function(scope, element, attrs, imageDirectiveCtrl) {
element.bind("load", function() {
var e = this;
scope.$apply(function() {
imageDirectiveCtrl.childLoaded(e);
});
});
}
};
});
And the console output:
414 415
507 338
366 468
432 395
500 354
507 338
414 414
478 358
507 338
All children loaded.
The reason why you are printing undefined undefined
is because none of the images have been loaded at the time that you are doing your for
loop.
If you need to "process" (as you say) the results in the parent, then setting the value on the scope-exposed object may not be the best approach. How would you know that it's done loading, for example? How would you know that something has changed without an expensive deep $watch
?
The better approach, I think, is to expose an API that child directives could call. This is done by exposing a function on the parent's controller and using require: "^imageDirective"
on the child's.
.directive("imageDirective", function(){
return {
// .. whatever you currently have
controller: function($scope){
var toBeLoaded = 0;
this.registerChild = function(childElem){
toBeLoaded++;
};
this.childLoaded = function(childElem){
toBeLoaded--;
// do whatever processing you need
};
}
}
})
.directive("imageDirectiveItem", function(){
return {
require: "^imageDirective",
link: function(scope, element, attrs, imageDirectiveCtrl){
imageDirectiveCtrl.registerChild(element);
element.bind("load", function(){
scope.$apply(function(){
imageDirectiveCtrl.childLoaded(element);
})
})
}
}
}
If you are using a latest browser(IE >= 11) you can try MutationObserver
.
Try this in the link function of imageDirective
.
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var target = mutation.addedNodes && mutation.addNodes[0],
targetScope;
if (target) {
target = angular.element(target);
targetScope = target.scope();
console.log(targetScope.width, targetScope.height)
}
});
});
var config = {childList: true};
observer.observe(element[0], config);