├── README.md ├── controller.js └── preloader.js /README.md: -------------------------------------------------------------------------------- 1 | # Angular JS Easy Image Preloader 2 | 3 | Inspired heavily by [this] blog post. Credit for preloader code goes to Ben Nadel. 4 | 5 | ### Step 1: 6 | 7 | Include the preloader service in your controller: 8 | 9 | app.controller('MyController', function($scope, preloader) {} 10 | 11 | ### Step 2: 12 | 13 | Pass in an array of images for the preloader: 14 | 15 | $scope.imageLocations = [ 16 | "img/one.png", 17 | "img/two.png", 18 | "img/three.png", 19 | ... 20 | ]; 21 | 22 | ### Step 3: 23 | 24 | Call the preloader.preloadImages function, passing in your array of images: 25 | 26 | preloader.preloadImages( $scope.imageLocations ) 27 | 28 | preloader.preloadImages returns a promise, so you could also handle the promise like so: 29 | 30 | preloader.preloadImages( $scope.imageLocations ) 31 | .then(function() { 32 | // Loading was successful. 33 | }, 34 | function() { 35 | // Loading failed on at least one image. 36 | }); 37 | 38 | #### That's it! 39 | 40 | [this]: http://www.bennadel.com/blog/2597-preloading-images-in-angularjs-with-promises.htm -------------------------------------------------------------------------------- /controller.js: -------------------------------------------------------------------------------- 1 | app.controller('MyController', function($scope, preloader) { 2 | $scope.imageLocations = [ 3 | "img/one.png", 4 | "img/two.png", 5 | ]; 6 | // Preload the images; then, update display when returned. 7 | preloader.preloadImages( $scope.imageLocations ) 8 | }) -------------------------------------------------------------------------------- /preloader.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('core') 3 | .factory( 4 | 'preloader', 5 | function( $q, $rootScope ) { 6 | // I manage the preloading of image objects. Accepts an array of image URLs. 7 | function Preloader( imageLocations ) { 8 | // I am the image SRC values to preload. 9 | this.imageLocations = imageLocations; 10 | // As the images load, we'll need to keep track of the load/error 11 | // counts when announing the progress on the loading. 12 | this.imageCount = this.imageLocations.length; 13 | this.loadCount = 0; 14 | this.errorCount = 0; 15 | // I am the possible states that the preloader can be in. 16 | this.states = { 17 | PENDING: 1, 18 | LOADING: 2, 19 | RESOLVED: 3, 20 | REJECTED: 4 21 | }; 22 | // I keep track of the current state of the preloader. 23 | this.state = this.states.PENDING; 24 | // When loading the images, a promise will be returned to indicate 25 | // when the loading has completed (and / or progressed). 26 | this.deferred = $q.defer(); 27 | this.promise = this.deferred.promise; 28 | } 29 | // --- 30 | // STATIC METHODS. 31 | // --- 32 | // I reload the given images [Array] and return a promise. The promise 33 | // will be resolved with the array of image locations. 34 | Preloader.preloadImages = function( imageLocations ) { 35 | var preloader = new Preloader( imageLocations ); 36 | return( preloader.load() ); 37 | }; 38 | // --- 39 | // INSTANCE METHODS. 40 | // --- 41 | Preloader.prototype = { 42 | // Best practice for "instnceof" operator. 43 | constructor: Preloader, 44 | // --- 45 | // PUBLIC METHODS. 46 | // --- 47 | // I determine if the preloader has started loading images yet. 48 | isInitiated: function isInitiated() { 49 | return( this.state !== this.states.PENDING ); 50 | }, 51 | // I determine if the preloader has failed to load all of the images. 52 | isRejected: function isRejected() { 53 | return( this.state === this.states.REJECTED ); 54 | }, 55 | // I determine if the preloader has successfully loaded all of the images. 56 | isResolved: function isResolved() { 57 | return( this.state === this.states.RESOLVED ); 58 | }, 59 | // I initiate the preload of the images. Returns a promise. 60 | load: function load() { 61 | // If the images are already loading, return the existing promise. 62 | if ( this.isInitiated() ) { 63 | return( this.promise ); 64 | } 65 | this.state = this.states.LOADING; 66 | for ( var i = 0 ; i < this.imageCount ; i++ ) { 67 | this.loadImageLocation( this.imageLocations[ i ] ); 68 | } 69 | // Return the deferred promise for the load event. 70 | return( this.promise ); 71 | }, 72 | // --- 73 | // PRIVATE METHODS. 74 | // --- 75 | // I handle the load-failure of the given image location. 76 | handleImageError: function handleImageError( imageLocation ) { 77 | this.errorCount++; 78 | // If the preload action has already failed, ignore further action. 79 | if ( this.isRejected() ) { 80 | return; 81 | } 82 | this.state = this.states.REJECTED; 83 | this.deferred.reject( imageLocation ); 84 | }, 85 | // I handle the load-success of the given image location. 86 | handleImageLoad: function handleImageLoad( imageLocation ) { 87 | this.loadCount++; 88 | // If the preload action has already failed, ignore further action. 89 | if ( this.isRejected() ) { 90 | return; 91 | } 92 | // Notify the progress of the overall deferred. This is different 93 | // than Resolving the deferred - you can call notify many times 94 | // before the ultimate resolution (or rejection) of the deferred. 95 | this.deferred.notify({ 96 | percent: Math.ceil( this.loadCount / this.imageCount * 100 ), 97 | imageLocation: imageLocation 98 | }); 99 | // If all of the images have loaded, we can resolve the deferred 100 | // value that we returned to the calling context. 101 | if ( this.loadCount === this.imageCount ) { 102 | this.state = this.states.RESOLVED; 103 | this.deferred.resolve( this.imageLocations ); 104 | } 105 | }, 106 | // I load the given image location and then wire the load / error 107 | // events back into the preloader instance. 108 | // -- 109 | // NOTE: The load/error events trigger a $digest. 110 | loadImageLocation: function loadImageLocation( imageLocation ) { 111 | var preloader = this; 112 | // When it comes to creating the image object, it is critical that 113 | // we bind the event handlers BEFORE we actually set the image 114 | // source. Failure to do so will prevent the events from proper 115 | // triggering in some browsers. 116 | // -- 117 | // The below removes a dependency on jQuery, based on a comment 118 | // on Ben Nadel's original blog by user Adriaan: 119 | // http://www.bennadel.com/members/11887-adriaan.htm 120 | var image = angular.element( new Image() ) 121 | .bind('load', function( event ) { 122 | // Since the load event is asynchronous, we have to 123 | // tell AngularJS that something changed. 124 | $rootScope.$apply( 125 | function() { 126 | preloader.handleImageLoad( event.target.src ); 127 | // Clean up object reference to help with the 128 | // garbage collection in the closure. 129 | preloader = image = event = null; 130 | } 131 | ); 132 | }) 133 | .bind('error', function( event ) { 134 | // Since the load event is asynchronous, we have to 135 | // tell AngularJS that something changed. 136 | $rootScope.$apply( 137 | function() { 138 | preloader.handleImageError( event.target.src ); 139 | // Clean up object reference to help with the 140 | // garbage collection in the closure. 141 | preloader = image = event = null; 142 | } 143 | ); 144 | }) 145 | .attr( 'src', imageLocation ) 146 | ; 147 | } 148 | }; 149 | // Return the factory instance. 150 | return( Preloader ); 151 | } 152 | ); --------------------------------------------------------------------------------