2 way binding issues with directives, controllers and services - angularjs

This is bugging me a bit.
I have a service that handles logo panels and a function that is used to navigate between the different panels.
When getPanels is invoked it sets the currentPanel, index and length on the service when all promises have completed (see $q.all in the getPanels method):
.service('ConfiguratorLogoService', ['$q', 'UploadService', 'LogoService', 'ArrayService', 'SvgService', function ($q, uploadService, logoService, arrayService, helper) {
// Private function to build a file array
var _buildFileArray = function (panels, files) {
//--- Omitted for brevity ---//
};
// Create our service
var service = {
// Create our arrays
panels: [],
files: [],
currentPanel: null,
index: 0,
length: 0,
// Get our panels
getPanels: function (container, garmentId) {
// Create a deferred promise
var deferred = $q.defer();
// Create our arrays
var panels = []
files = [],
promises = [];
// If we have a container
if (container) {
// Get the containers children
var children = container.children()
// Loop through our panel's children
for (var i = 0; i < children.length; i++) {
// Get the current child
var child = angular.element(children[i]),
childId = child.attr('id'),
childTitle = helper.extractText(childId, ':', 1);
// Create our item
var panel = {
id: childId,
title: childTitle
};
// Try to get our item
promises.push(logoService.get(garmentId, panel.id).then(function (response) {
// If we have any data
if (response) {
// Add the file to our array
files.push(response);
}
}));
// Add our child to the array
panels.push(panel);
}
}
// After all the promises have been handled
$q.all(promises).then(function () {
// Get our files
service.files = _buildFileArray(panels, files);
service.panels = panels;
service.currentPanel = panels[0];
service.length = panels.length;
// Resolve our promise
deferred.resolve({
files: service.files,
panels: panels
});
});
// Return our promise
return deferred.promise;
},
// Get our next panel
navigateNext: function () {
// Create a deferred promise
var deferred = $q.defer();
// Get the next index or reset if we reached the end of our list
service.index = service.index === (service.length - 1) ? 0 : service.index += 1;
// Set our active panel
service.currentPanel = service.panels[service.index];
console.log(service.index);
// Resolve our promise
deferred.resolve();
// Return our promise
return deferred.promise;
},
// Get our previous panel
navigatePrevious: function () {
// Get the previous index or set to the end of our list
service.index = service.index === 0 ? service.length - 1 : service.index -= 1;
// Set our active panel
service.currentPanel = service.panels[service.index];
},
// Removes the file from azure
remove: function (index) {
//--- Omitted for brevity ---//
}
};
// Return our service
return service;
}])
which is fine, it works and the first panel is selected.
So, I have a controller, which is attached to a directive. The controller looks like this:
.controller('ConfiguratorLogosDirectiveController', ['ConfiguratorLogoService', 'RowService', function (service, rowService) {
var self = this;
// Set our current panel
self.currentPanel = service.currentPanel;
self.index = service.index;
self.length = service.length;
// Initialization
self.init = function (container, garmentId) {
// Get our panels
return service.getPanels(container, garmentId).then(function (response) {
self.panels = response.panels;
self.files = response.files;
// If we have any panels
if (self.panels.length) {
// Set our current panel
self.currentPanel = service.currentPanel;
self.index = service.index;
self.length = service.length;
}
// Return our response
return response;
})
};
// Map our service functions
self.upload = service.upload;
self.next = service.navigateNext;
self.previous = service.navigatePrevious;
self.remove = service.remove;
}])
As you can see, when I get my panels, I set the currentPanel, index and length on the controller itself which I didn't think I would have to do because when the controller is invoked, it already has a reference to the service values. I figured 2 way binding would come into play and when the service values update, the controller would update too.
Anyway, I update the values after the getPanels method completes successfully. In my directive I have this:
// Invoke on controller load
controller.init(container, scope.garmentId).then(function (response) {
// Map our properties
scope.panels = controller.panels;
scope.files = controller.files;
scope.currentPanel = controller.currentPanel;
scope.index = controller.index;
scope.length = controller.length;
});
which again works fine. In my template I can see the first panel and it looks fine.
So, then came the next step which was my navigate functions. I started with next which I have modified for testing purposes so I can output the controller.index as well as the console.log in the service navigation function.
The directive function looks like this:
scope.next = function () {
controller.next().then(function () {
console.log(controller.index);
});
};
When this method is invoked, I can see in my console that the service increases the index by 1 but the controller still shows 0 which means that 2 way binding is not working.
I am about to update my method in the controller to push the currentPanel and index to the controller, but before I do I thought I would ask here first.
So, does anyone know why my 2 way binding isn't working?
So my current workaround works, but I just don't like it.
In my directive I have done this:
scope.next = function () {
controller.next().then(function () {
console.log(controller.index);
scope.currentPanel = controller.currentPanel;
scope.index = controller.index;
scope.length = controller.length;
});
}
and in the directive controller I have done this:
self.next = function () {
// Try to navigate forward
return service.navigateNext().then(function () {
// Set our current panel
self.currentPanel = service.currentPanel;
self.index = service.index;
self.length = service.length;
console.log(self.index);
});
}
and in my service, it looks the same as before:
// Get our next panel
navigateNext: function () {
// Create a deferred promise
var deferred = $q.defer();
// Get the next index or reset if we reached the end of our list
service.index = service.index === (service.length - 1) ? 0 : service.index += 1;
// Set our active panel
service.currentPanel = service.panels[service.index];
console.log(service.index);
// Resolve our promise
deferred.resolve();
// Return our promise
return deferred.promise;
},
This works, but surely this is not the way it should work.

I have figured it out thanks to this article.
I just had to create an object in my directive and bind the values to that.
Doing that fixed the issues.

Related

AngularJS: Passing Promise, yet Cannot read property 'finally' of undefined

In my angular app, I have 2 methods save() and saveTriggers(). saveTriggers() updates all records by calling a web service (C#). I want to make sure that a block of code is executed after all records are updated in saveTriggers() and control is returned to save(). I believe I need to pass something from the saveTriggers() to make finally block execute. I tried various things, nothing works. Using .then() also gives the same error. I am not that good at JS. Can you please guide me.
vm.updatedTriggers = []; // IDs are pushed in
vm.saveTriggers = function () {
if (vm.updatedTriggers.length === 0) {
vm.close();
} else {
vm.saving = true;
vm.save()
.finally(function () { // ERROR - Cannot read property 'finally' of undefined
console.log("Saved all. Closing..."); // Never REACHES here
vm.saving = false;
vm.updated = true;
$uibModalInstance.close(true);
});
}
};
vm.save = function () {
//vm.saving = true;
for (var i = 0; i < vm.updatedTriggers.length; i++) {
var trigger = vm.triggers.find(t => t.id === vm.updatedTriggers[i]);
var input = {
id: trigger.id,
target: trigger.target,
targetInfo: vm.targetData,
event: trigger.event,
eventQuantity: trigger.eventQuantity,
eventQuantityExtra: trigger.eventQuantityExtra
};
rpmService.editDeviceTrigger(input);
/*.finally(function () {
console.log("Updated event"); // Reaches here
vm.updated = true;
return Promise.resolve(2);
});*/ // Commenting this also doesn't help
}
return Promise.resolve(2);
};
rpmService.editDeviceTrigger(input)
public async Task EditDeviceTrigger(EditDeviceTriggerInput input) {
// calls other methods with await
// Doesn't return anything
}
EDIT: Updated Code: I got rid of the error, but the output is not is expected series.
vm.saveTriggers = function () {
vm.saving = true;
vm.save().then
(function success() {
console.log("Returned Result ");
console.log("Saved all. Closing..."); // These lines are executed before the event is upated
vm.saving = false;
$uibModalInstance.close(true);
});
};
vm.save = function () {
var deferred = $q.defer();
for (var i = 0; i < vm.updatedTriggers.length; i++) {
var trigger = vm.triggers.find(t => t.id === vm.updatedTriggers[i]);
var input = {
id: trigger.id,
....
};
rpmService.editDeviceTrigger(input)
.finally(function () {
console.log("Updated event"); // Successfully updates all events
vm.updated = true;
});
}
deferred.resolve();
return deferred.promise;
};
OUTPUT:
Returned Result
Saved all. Closing...
Updated event
EXPECTED OUTPUT:
Updated event
Returned Result
Saved all. Closing...
Thanks.
Usually you dont need $q.defer-related things, but u can do same using it if u want.
Here I guess you just need to collect all your save promises and return new resulting one using $q.all:
vm.save = function () {
const myAwesomePromises = []
for (var i = 0; i < vm.updatedTriggers.length; i++) {
...
const savePromise = rpmService.editDeviceTrigger(input);
savePromise.finally(() => console.log('edit device finally'));// <-- not sure u need this
myAwesomePromises.push(savePromise);
}
return $q.all(myAwesomePromises).finally(() => console.log('All edit device finally'));
};

Angular template won't load. Even with $loaded. Data resolves after Load

Using AngularFire, Angular, Firebase.
I load a list of users from a Firebase Database. I use $loaded to ensure it waits until data loads.
I take this list, compare it against another firebase database of groups and push the results into two arrays.
Based on the console.logs the data sorts correctly. However, inside my template I get a blank page (I think this is because the page loads before the data is sorted).
Thoughts?
let userLoggedIn = AuthFactory.getUser();
var allUsersArray = $firebaseArray(ConnectFactory.fbUserDb);
var x = firebase.database().ref('groups');
var friendArr = [];
var notFriendArr = [];
allUsersArray.$loaded().then(function(){
angular.forEach(allUsersArray, function(user, i) {
var haveIAdded = x.child(userLoggedIn).child(allUsersArray[i].uid).once('value').then(function (snap) {
if (snap.val() !== null) {
return true;
} else {
return false;
}
});
var haveTheyAdded = x.child(allUsersArray[i].uid).child(userLoggedIn).once('value').then(function (snap) {
if (snap.val() !== null) {
return true;
} else {
return false;
}
});
Promise.all([haveIAdded, haveTheyAdded]).then(function([you, they]) {
if (you && they) {
console.log('We Are Friends', allUsersArray[i]);
friendArr.push(allUsersArray[i]);
} else {
console.log('not a friend ', allUsersArray[i]);
notFriendArr.push(allUsersArray[i]);
}
});
});
$scope.friendList = friendArr;
$scope.notFriendList = notFriendArr;
});
Alright, this time I tried to actually read the question before attempting to answer. ;-)
When you set your $scope.friendList and $scope.notFriendList within the $loaded promise, your Promise.all may (and most likely) havn't resolved yet when those are called, since angular.forEach doesn't wait for the promises to finish before moving on to the next statement in the function. So you'll have to build an array of promises and wait for them all to resolve outside of the loop before attempting to set your $scope variables.
allUsersArray.$loaded().then(function(){
var promises = [];
var friendArr = [];
var notFriendArr = [];
angular.forEach(allUsersArray, function(user, i) {
... // Same as before
promises.push(
Promise.all([haveIAdded, haveTheyAdded]).then(function([you, they]) {
if (you && they) {
console.log('We Are Friends', allUsersArray[i]);
friendArr.push(allUsersArray[i]);
} else {
console.log('not a friend ', allUsersArray[i]);
notFriendArr.push(allUsersArray[i]);
}
})
);
});
Promise.all(promises).then(function(){
$scope.friendList = friendArr;
$scope.notFriendList = notFriendArr;
});
});

angularjs 2way binding between service and controller with an array

Before I start, I will link my codepen so you can have a look before reading this :)
I have made a very simple app to demonstrate my issue.
basically when I have multiple services sharing an array, it appears that angularjs treats the array as a primitive object which is odd.
I have to link code, so this is my controller and 2 services at the start:
angular.module('bindingApp', [])
.controller('MainController', ['MainService', 'SelectionsService', function (service, selections) {
// Map the controller to a variable
var self = this;
// Attach the model to our scope
self.models = service.models;
// Create a function that handles the changes the service in some way
self.updateSelectedItems = function () {
// Get our first item
var models = selections.selected;
// Loop through our selected items
for(var i = 0; i < models.length; i++) {
// Get our current model
var model = models[i];
// Change the properties
model.status = 'Cancelled';
model.colour = 'gray';
}
};
// Reset everything
self.reset = function () {
selections.reset();
service.reset();
console.log('models in the controller', self.models);
}
// Map our function to our service function
self.selectItem = selections.select;
// Initialize our service
service.init();
}])
.service('MainService', function () {
// Create our service
var service = {
// Create the default model (empty array)
models: [],
// Populates our model
init: function () {
// Loop from 1 to 10
for(var i = 0; i < 10; i++){
// Create some object
var model = {
status: 'Live',
colour: 'green',
selected: false
};
// Push our model to our array
service.models.push(model);
}
console.log('models in the service', service.models);
},
// Resets everything
reset: function () {
// Reset our array
service.models = [];
// Initialize
service.init();
}
};
// Return our service
return service;
})
.service('SelectionsService', function () {
// Create our service
var service = {
// Create a reference to our selected objects
selected: [],
// Our select function
select: function (e, item) {
// Create a reference to our selected items
var selected = service.selected;
// Update the selected status
item.selected = true;
// Push our item to our selected items
selected.push(item);
// Stop propagation
e.stopPropagation();
e.preventDefault();
},
// Resets our selected items
reset: function () {
service.selected = [];
}
};
// Return our service
return service;
});
I have created 2 more Codepens to try to fix the issue. The first one maps the items to an object encapsulating an array in the MainController. The second one does the same, but also the MainService has an object encapsulating the array.
Does anyone know what I can do to get this simple pen working?
OK. This doesn't have much to do with Angular. Just plain JavaScript.
Here's what happens:
The service initializes its models array. service.models is a variable that contains a reference to that array. You thus have
service.models ----> [firstArray]
Then the controller does self.models = service.models;. So it creates another reference pointing to the same array, referenced by service.models:
service.models ----> [firstArray]
^
controller.models ----|
Then you click the reset button, which calls service.reset(), which does service.models = [];. So it creates a new array and assigns it to its models variable. You thus end up with
service.models ----> []
controller.models --> [firstArray]
Note that you never make controller.models point to the new array in the service. So it still points to the first array, and the view still displays the elements of that first array.
So, either you make sure to reinitialize the controller's models variable after calling reset():
controller.models = service.models;
Or you make sure to simply remove everything from the original, first array in reset():
service.models.splice(0, service.models.length)
It looks like that you must not reset the models array by referencing a new empty array but removing all items from it.
Instead
// Reset our array
service.models = [];
use
// Reset our array
service.models.length = 0;

AngularJS chaining promises - need to do work before the next 'then'

I am working on a promise chain. The first call is an $http call to check if a user exists, and then if it does, theres a bunch of .then() statements that run sequentially.
My question is this.. in that first call, i don't want to return the promise of the $http request because if the user doesn't exist, the results are just an empty array and the promise resolves, thus triggering the next action to look up information about the user. I wrote the following code...
(see the part in comments about being the important part i'm asking about)
$scope.checkIfUserExists = function() {
if (angular.isObject($scope.admin.Inductee.Contactor)) {
var handleFault = function( fault ) {
if (typeof(fault) === 'string') {
switch (fault.toUpperCase()){
case 'NODATA':
// Go ahead an save
$scope.pushInductee();
break;
case 'STATUS':
// just get the 'duplicate records check' sign off of there
// The save button is disabled by the critical error
$scope.hideSave = false;
break;
case 'ASSIGNED':
// just get the 'duplicate records check' sign off of there
// The save button is disabled by the critical error
$scope.hideSave = true;
break;
default:
$log.error(fault);
$location.path('/error/default');
}
} else {
$log.error(fault);
$location.path('/error/default');
}
};
$scope.getMatchingIndData()
.then($scope.procBusLogic)
.then($scope.pushInductee)
.catch(handleFault);
}
};
////HERE IS THE IMPORTANT PART I AM ASKING ABOUT
$scope.getMatchingIndData = function() {
var deferred = $q.defer();
var locals = {};
var checkUser = function(dupeJson){
var checkUserDeferred = $q.defer();
// abandoned promise replaced with my own
sttiJoinDataFactory.checkIfUserExistsNurseleader(dupeJson)
.then(function(results) {
var data = results.data;
if (angular.isArray(data) && data.length > 0){
var highestMatch = data[0];
for (var i = 0; i < data.length; i++) {
if (parseInt(data[i].Score) > parseInt(highestMatch.Score)) {
highestMatch = data[i];
}
}
checkUserDeferred.resolve(highestMatch);
} else {
// Reject the 'overall' promise here
// to effectively break the chain
return deferred.reject('NODATA');
}
})
.catch(function(fault) {
// Any other failure should break the chain
// of http requests at this point
return deferred.reject(fault);
});
return checkUserDeferred.promise;
},
loadindividual = function (highestMatch) {
return $http stuff about the highestmatch
// set data in locals
},
parallelLoadStatusAndInducteeData = function(individual) {
return another $http promise based on the last then()
// set data in locals
},
loadCeremonyData = function (inductees){
return another $http promise based on the last call then() // set data in locals
},
reportProblems = function( fault ) {
deferred.reject(fault);
};
checkUser($scope.generateDupJson())
.then(loadindividual, reportProblems)
.then(parallelLoadStatusAndInducteeData, reportProblems)
.then(loadCeremonyData, reportProblems)
.then(function() {
deferred.resolve(locals);
})
.catch( reportProblems );
return deferred.promise;
};
Must I take into account the abandoned promise, since I really need to promise to resolve when the data comes back, and i need to reject it if there is NODATA. This is handled in the calling function's chain.
Also, I'm aware of antipatterns here. I'm trying my best to not nest promises, maintain the chain, as well as handle exceptions.
Ok I have a few comments for you:
...
// revert if and return immediately
// to reduce indentation
if (typeof(fault) !== 'string') {
$log.error(fault);
$location.path('/error/default');
return;
}
switch (fault.toUpperCase()) {
...
You don't need deferred objects:
var checkUser = function(dupeJson){
// this is not abandoned because we are returning it
return sttiJoinDataFactory.checkIfUserExistsNurseleader(dupeJson)
.then(function(results) {
var data = results.data;
if (!angular.isArray(data) || data.length <= 0) {
return $q.reject('NODATA');
}
var highestMatch = data.reduce(function (highest, d) {
return parseInt(d.Score) > parseInt(highest.Score) ?
d : highest;
}, data[0]);
return highestMatch;
}); // you don't need catch here if you're gonna reject it again
}
...
checkUser(...)
// loadIndividual will be called
// after everything inside checkUser resolves
// so you will have your highestMatch
.then(loadIndividual)
.then(parallelLoadStatusAndInducteeData)
.then(loadCeremonyData)
// you don't need to repeat reportProblems, just catch in the end
// if anything rejects prior to this point
// reportProblems will be called
.catch(reportProblems)
...

Angular preloader start after everything else has loaded

Hopefully this is an easy answer.
I have this directive that controls the background images on the site. It rotates between images, but before it starts setting the background images, it actually preloads them. This is my code for the directive:
// ---
// CONTROLLERS.
// ---
.controller('BackgroundSwitcherController', ['$rootScope', 'Options', 'BackgroundSwitcherService', function ($rootScope, options, service) {
var self = this;
// Declare our variables
self.model = {
staticImage: '',
loading: false,
rotate: false
};
// On state change
$rootScope.$on('$stateChangeStart', function (event, toState) {
// Get our data
var data = toState.data;
// Set our rotate flag
self.model.rotate = options.current.rotateBackground;
// If we are set to rotate
if (self.model.rotate) {
// Make a call to our service (this is called every state change, but only preloaded once)
service.get().then(function (response) {
// Return the images in the response
self.model.images = response;
});
} else {
// Otherwise, reset the images
self.model.images = null;
}
// If we have data
if (data) {
// Reset our variables
self.model.staticImage = data.backgroundImage;
//self.model.loading = true;
}
});
}])
// ---
// SERVICES.
// ---
.service('BackgroundSwitcherService', ['$q', 'PreloaderService', function ($q, preloader) {
this.loading = true;
this.successful = false;
this.percentLoaded = 0;
// Get our images
this.get = function () {
// Our images
var images = [
'assets/backgrounds/Avebury Steps.jpg',
'assets/backgrounds/Avebury Stripe.jpg',
'assets/backgrounds/Avebury.jpg',
'assets/backgrounds/Forest Hills.jpg',
'assets/backgrounds/Inteface.jpg',
'assets/backgrounds/New Oaklands.jpg',
'assets/backgrounds/Primo Delight.jpg',
'assets/backgrounds/Secure 3d.jpg',
'assets/backgrounds/Secure.jpg',
'assets/backgrounds/Sensation Heathers.jpg',
'assets/backgrounds/Sensation.jpg',
'assets/backgrounds/Woman.jpg'
];
// Create our promise
var deferred = $q.defer();
// Preload the images
preloader.preloadImages(images).then(
// Function to handle the changing of the flags when all images have loaded
function handleResolve(imageLocations) {
this.loading = false;
this.successful = true;
deferred.resolve(images);
},
// Function to handle any errors
function handleReject(imageLocation) {
this.loading = false;
this.successful = false;
deferred.reject();
},
// Function that notifies our percentage loaded flag
function handleNotify(event) {
this.percentLoaded = event.percent;
}
);
// Return our promise
return deferred.promise;
};
}])
// ---
// DIRECTIVES.
// ---
.directive('backgroundSwitcher', ['$interval', function ($interval) {
// Variables
var target = null,
timer = null,
images = null,
currentIndex = 0;
// Function to get a random value between two numbers
function getRandomInt(min, max) {
// Return our random number
return Math.floor(Math.random() * (max - min)) + min;
};
var getImageIndex = function (length) {
// Get our random index
var index = getRandomInt(0, images.length);
// If our index matches the current index
if (index == currentIndex) {
// Run again until we get a different index
return getImageIndex(length);
}
// Set our current index our the new index
currentIndex = index;
// Return our index
return index;
}
// Apply the image
var applyImage = function () {
// Get a random image
var image = images[getImageIndex(images.length)];
// Apply our image to our target
applyStaticImage(image);
};
// Apply a static image
var applyStaticImage = function (image) {
// Apply our image to our target
target.css('background-image', 'url(' + escape(image) + ')');
};
// Remove any images
var removeImage = function () {
// Remove our background image
target.css('background-image', 'none');
};
// Start timer function
var startTimer = function () {
// If our timer is not running
if (!timer || timer.$$state.status === 2) {
// Apply our first image
applyImage();
// Start our timer
timer = $interval(changeImage, 10000);
}
};
// Function to change the background image
var changeImage = function () {
// Apply our image
applyImage();
};
// Stop the timer
var stopTimer = function () {
// If we have started our timer
if (timer) {
// Stop it
$interval.cancel(timer);
}
};
return {
restrict: 'A',
controller: 'BackgroundSwitcherController',
link: function (scope, element, attr, controller) {
// Assign our element to our global variable
target = element;
// Watch our image
scope.$watch(function () {
// Return our image
return controller.model.staticImage;
}, function (staticImage) {
// If we have an image
if (staticImage) {
// Stop our timer
stopTimer();
// Apply our static image
applyStaticImage(staticImage);
} else {
// If we are not rotating
if (!controller.model.rotate) {
// Remove any images
removeImage();
}
}
})
// Watch our rotate
scope.$watch(function () {
// Return our rotate flag
return controller.model.images;
}, function (array) {
// Set our variable
images = array;
// If we have some images
if (images) {
// If we don't have a static image
if (!controller.model.staticImage) {
// Start rotating our images
startTimer();
}
} else {
// Remove any images
removeImage();
// Otherwise, stop our timer
stopTimer();
}
});
// Destroy function to cancel all timers
element.on('$destroy', function () {
// Stop our timer
stopTimer();
});
}
}
}]);
and for the actual preloader I have this:
// ---
// SERVICES.
// ---
.factory('PreloaderService', function ($q, $rootScope) {
function Preloader(imageLocations) {
this.imageLocations = imageLocations;
this.imageCount = imageLocations.length;
this.loadCount = 0;
this.errorCount = 0;
this.states = {
PENDING: 1,
LOADING: 2,
RESOLVED: 3,
REJECTED: 4
};
this.state = this.states.PENDING;
// When loading the images, a promise will be returned to indicate
// when the loading has completed (and / or progressed).
this.deferred = $q.defer();
this.promise = this.deferred.promise;
}
// I reload the given images [Array] and return a promise. The promise
// will be resolved with the array of image locations.
Preloader.preloadImages = function (imageLocations) {
var preloader = new Preloader(imageLocations);
return (preloader.load());
};
Preloader.prototype = {
// Best practice for 'instnceof' operator.
constructor: Preloader,
// ---
// PUBLIC METHODS.
// ---
// I determine if the preloader has started loading images yet.
isInitiated: function isInitiated() {
return (this.state !== this.states.PENDING);
},
// I determine if the preloader has failed to load all of the images.
isRejected: function isRejected() {
return (this.state === this.states.REJECTED);
},
// I determine if the preloader has successfully loaded all of the images.
isResolved: function isResolved() {
return (this.state === this.states.RESOLVED);
},
// I initiate the preload of the images. Returns a promise.
load: function load() {
// If the images are already loading, return the existing promise.
if (this.isInitiated()) {
return (this.promise);
}
this.state = this.states.LOADING;
for (var i = 0 ; i < this.imageCount ; i++) {
this.loadImageLocation(this.imageLocations[i]);
}
// Return the deferred promise for the load event.
return (this.promise);
},
// ---
// PRIVATE METHODS.
// ---
// I handle the load-failure of the given image location.
handleImageError: function handleImageError(imageLocation) {
this.errorCount++;
// If the preload action has already failed, ignore further action.
if (this.isRejected()) {
return;
}
this.state = this.states.REJECTED;
this.deferred.reject(imageLocation);
},
// I handle the load-success of the given image location.
handleImageLoad: function handleImageLoad(imageLocation) {
this.loadCount++;
// If the preload action has already failed, ignore further action.
if (this.isRejected()) {
return;
}
// Notify the progress of the overall deferred. This is different
// than Resolving the deferred - you can call notify many times
// before the ultimate resolution (or rejection) of the deferred.
this.deferred.notify({
percent: Math.ceil(this.loadCount / this.imageCount * 100),
imageLocation: imageLocation
});
// If all of the images have loaded, we can resolve the deferred
// value that we returned to the calling context.
if (this.loadCount === this.imageCount) {
this.state = this.states.RESOLVED;
this.deferred.resolve(this.imageLocations);
}
},
// I load the given image location and then wire the load / error
// events back into the preloader instance.
// --
// NOTE: The load/error events trigger a $digest.
loadImageLocation: function loadImageLocation(imageLocation) {
var preloader = this;
// When it comes to creating the image object, it is critical that
// we bind the event handlers BEFORE we actually set the image
// source. Failure to do so will prevent the events from proper
// triggering in some browsers.
var image = angular.element(new Image());
image.bind('load', function (event) {
// Since the load event is asynchronous, we have to
// tell AngularJS that something changed.
$rootScope.$apply(
function () {
preloader.handleImageLoad(event.target.src);
// Clean up object reference to help with the
// garbage collection in the closure.
preloader = image = event = null;
}
);
}).bind('error', function (event) {
// Since the load event is asynchronous, we have to
// tell AngularJS that something changed.
$rootScope.$apply(
function () {
preloader.handleImageError(event.target.src);
// Clean up object reference to help with the
// garbage collection in the closure.
preloader = image = event = null;
}
);
}).prop('src', imageLocation);
}
};
// Return the factory instance.
return (Preloader);
});
Now, when I use this directive all my other images / font awesome icons pause before they load. It causes an issue because the font awesome icons actually briefly show a rectangular box before fully loading and the profile image is blank until loaded.
If I remove my directive from my HTML everything loads fine.
Now I realise that this is happening because it has to load an array of images for the background switcher, so what I would like to do is to not start the preloading until everything else has loaded first.
Can someone tell me if there is a simple way to do this?

Resources