Reputation: 1482
I'm working on search UI. There is a list view of results and each result can be "expanded" to show more data. When the user expands the result, I asynchronously fetch the details which can contain from zero to 12 pictures. This is done by a "detail service," which is a service in the angular sense.
Now, because I am aiming for a "native like feel" and these images can be weighty, I don't want them to appear until they have finished loading.
The question is: how does the service notify its client (a controller) as each image loads?
Many articles recommend promises as the best way to do this. Naively, I would think to return an array of promises - one for each image - that will resolve after the image loads.
This seems like a heavy-handed solution, requiring a loop attaching then() functions to an arbitrary number of promises.
Another popular solution is to use broadcast() on the $rootScope. This seems "sloppy" in that I know exactly who is "listening" for the result.
Is the "array of promises" correct, or is there a more concise or "Angular" way?
NOTE: these are independent results and should not be chained or composed into a single promise.
EDIT: apologize for the unclear ask. The desired functionality is that each picture appears as it is loaded. So aggregating promises is undesired. That leaves the core ask as whether promises are the most concise solve for this. I found what I think is a better one, expressed in the answers as an image directive that delays display until the image is loaded. Moving this logic out of the service and into a directive seems a more elegant solution.
Upvotes: 0
Views: 147
Reputation: 486
you can write something like this (only $http.get services in this example):
var getAllServicesResult = function (servicesList) {
var promises = [];
angular.forEach(servicesList, function (s, index) {
var deffered = $q.defer();
$http.get(s.path, { params: s.params }).success(function (response) {
deffered.resolve(response);
}).error(function (error) {
deffered.reject();
});
promises.push(deffered.promise);
});
return $q.all(promises);
}
servicesList is an array like this :
servicesList =[{path: '/Reference/GetRefrenceByType'
, params: { refType: d.Service_Ref_Field_Name, baseMapType: 0 }}
,{},...];
Upvotes: 0
Reputation: 1482
After playing with a few approaches, it seems that moving the image-load-check to a directive results in the most concise, reusable, and readable solution.
// directive for an "optional background image" element that will only display if and
// after the image loads
.directive('optBgImg',
function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
// create an image object to get a reliable onload event
// (using load on the element unreliable for dynamic divs)
var img = new Image();
// onload handler (on the image, not the element)
img.onload = function() {
img.onload = null;
// add a class to reveal the image
element.addClass("loaded");
// set the background image on the element
// (using background image for advanced formatting)
element.css("background-image", "url(" +
attrs.optBgImg + ")");
};
img.onerror = function() {
img.onerror = null;
// could remove the element here for extra credit
};
// set the source for the image to initiate the load
img.src = attrs.optBgImg;
}
};
});
Upvotes: 0
Reputation: 52847
Each promise in the array of promises execute in parallel and asynchronously. $q.all lets you handle when they all resolve successfully.
Upvotes: 0
Reputation: 1976
I would say that the 'array of promises' is the correct approach.
You can use the .all() method which takes any number of promises as arguments (as an array). You can then resolve this promise as a single promise.
Returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
$q.all([firstPromise.then(function(){
//do something
}),
secondPromise.then(function(){
//do something else
})
])
.then(function(){
//do something after all promises are resolved
});
https://docs.angularjs.org/api/ng/service/$q
Upvotes: 1