1
+ <!doctype html>
2
+ < html ng-app ="Demo ">
3
+ < head >
4
+ < meta charset ="utf-8 " />
5
+
6
+ < title >
7
+ Preloading Images In AngularJS With Promises
8
+ </ title >
9
+ </ head >
10
+ < body ng-controller ="AppController ">
11
+
12
+ < h1 >
13
+ Preloading Images In AngularJS With Promises
14
+ </ h1 >
15
+
16
+ < div ng-switch ="isLoading ">
17
+
18
+ <!-- BEGIN: Loading View. -->
19
+ < p ng-switch-when ="true ">
20
+
21
+ Your images are being loaded... {{ percentLoaded }}%
22
+
23
+ </ p >
24
+ <!-- END: Loading View. -->
25
+
26
+ <!-- BEGIN: Results View. -->
27
+ < div
28
+ ng-switch-when ="false "
29
+ ng-switch ="isSuccessful ">
30
+
31
+ < p ng-switch-when ="true ">
32
+
33
+ < img
34
+ ng-repeat ="src in imageLocations "
35
+ ng-src ="{{ src }} "
36
+ style ="width: 100px ; margin-right: 10px ; "
37
+ />
38
+
39
+ </ p >
40
+
41
+ < p ng-switch-when ="false ">
42
+
43
+ < strong > Oops</ strong > : One of your images failed to load :(
44
+
45
+ </ p >
46
+
47
+ </ div >
48
+ <!-- END: Results View. -->
49
+
50
+ </ div >
51
+
52
+
53
+ <!-- Load scripts. -->
54
+ < script type ="text/javascript " src ="../../vendor/jquery/jquery-2.0.3.min.js "> </ script >
55
+ < script type ="text/javascript " src ="../../vendor/angularjs/angular-1.2.min.js "> </ script >
56
+ < script type ="text/javascript ">
57
+
58
+ // Create an application module for our demo.
59
+ var app = angular . module ( "Demo" , [ ] ) ;
60
+
61
+
62
+ // -------------------------------------------------- //
63
+ // -------------------------------------------------- //
64
+
65
+
66
+ // I control the root of the application.
67
+ app . controller (
68
+ "AppController" ,
69
+ function ( $scope , preloader ) {
70
+
71
+ // I keep track of the state of the loading images.
72
+ $scope . isLoading = true ;
73
+ $scope . isSuccessful = false ;
74
+ $scope . percentLoaded = 0 ;
75
+
76
+ // I am the image SRC values to preload and display./
77
+ // --
78
+ // NOTE: "cache" attribute is to prevent images from caching in the
79
+ // browser (for the sake of the demo).
80
+ $scope . imageLocations = [
81
+ ( "./ahhh.jpg?v=1&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
82
+ ( "./ahhh.jpg?v=2&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
83
+ ( "./ahhh.jpg?v=3&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
84
+ ( "./ahhh.jpg?v=4&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
85
+ ( "./ahhh.jpg?v=5&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
86
+ ( "./ahhh.jpg?v=6&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
87
+ ( "./ahhh.jpg?v=7&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
88
+ ( "./ahhh.jpg?v=8&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
89
+ ( "./ahhh.jpg?v=9&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
90
+ ( "./ahhh.jpg?v=10&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
91
+ ( "./ahhh.jpg?v=11&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
92
+ ( "./ahhh.jpg?v=12&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
93
+ ( "./ahhh.jpg?v=13&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
94
+ ( "./ahhh.jpg?v=14&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
95
+ ( "./ahhh.jpg?v=15&cache=" + ( new Date ( ) ) . getTime ( ) ) ,
96
+ ] ;
97
+
98
+ // Preload the images; then, update display when returned.
99
+ preloader . preloadImages ( $scope . imageLocations ) . then (
100
+ function handleResolve ( imageLocations ) {
101
+
102
+ // Loading was successful.
103
+ $scope . isLoading = false ;
104
+ $scope . isSuccessful = true ;
105
+
106
+ console . info ( "Preload Successful" ) ;
107
+
108
+ } ,
109
+ function handleReject ( imageLocation ) {
110
+
111
+ // Loading failed on at least one image.
112
+ $scope . isLoading = false ;
113
+ $scope . isSuccessful = false ;
114
+
115
+ console . error ( "Image Failed" , imageLocation ) ;
116
+ console . info ( "Preload Failure" ) ;
117
+
118
+ } ,
119
+ function handleNotify ( event ) {
120
+
121
+ $scope . percentLoaded = event . percent ;
122
+
123
+ console . info ( "Percent loaded:" , event . percent ) ;
124
+
125
+ }
126
+ ) ;
127
+
128
+ }
129
+ ) ;
130
+
131
+
132
+ // -------------------------------------------------- //
133
+ // -------------------------------------------------- //
134
+
135
+
136
+ // I provide a utility class for preloading image objects.
137
+ app . factory (
138
+ "preloader" ,
139
+ function ( $q , $rootScope ) {
140
+
141
+ // I manage the preloading of image objects. Accepts an array of image URLs.
142
+ function Preloader ( imageLocations ) {
143
+
144
+ // I am the image SRC values to preload.
145
+ this . imageLocations = imageLocations ;
146
+
147
+ // As the images load, we'll need to keep track of the load/error
148
+ // counts when announing the progress on the loading.
149
+ this . imageCount = this . imageLocations . length ;
150
+ this . loadCount = 0 ;
151
+ this . errorCount = 0 ;
152
+
153
+ // I am the possible states that the preloader can be in.
154
+ this . states = {
155
+ PENDING : 1 ,
156
+ LOADING : 2 ,
157
+ RESOLVED : 3 ,
158
+ REJECTED : 4
159
+ } ;
160
+
161
+ // I keep track of the current state of the preloader.
162
+ this . state = this . states . PENDING ;
163
+
164
+ // When loading the images, a promise will be returned to indicate
165
+ // when the loading has completed (and / or progressed).
166
+ this . deferred = $q . defer ( ) ;
167
+ this . promise = this . deferred . promise ;
168
+
169
+ }
170
+
171
+
172
+ // ---
173
+ // STATIC METHODS.
174
+ // ---
175
+
176
+
177
+ // I reload the given images [Array] and return a promise. The promise
178
+ // will be resolved with the array of image locations.
179
+ Preloader . preloadImages = function ( imageLocations ) {
180
+
181
+ var preloader = new Preloader ( imageLocations ) ;
182
+
183
+ return ( preloader . load ( ) ) ;
184
+
185
+ } ;
186
+
187
+
188
+ // ---
189
+ // INSTANCE METHODS.
190
+ // ---
191
+
192
+
193
+ Preloader . prototype = {
194
+
195
+ // Best practice for "instnceof" operator.
196
+ constructor : Preloader ,
197
+
198
+
199
+ // ---
200
+ // PUBLIC METHODS.
201
+ // ---
202
+
203
+
204
+ // I determine if the preloader has started loading images yet.
205
+ isInitiated : function isInitiated ( ) {
206
+
207
+ return ( this . state !== this . states . PENDING ) ;
208
+
209
+ } ,
210
+
211
+
212
+ // I determine if the preloader has failed to load all of the images.
213
+ isRejected : function isRejected ( ) {
214
+
215
+ return ( this . state === this . states . REJECTED ) ;
216
+
217
+ } ,
218
+
219
+
220
+ // I determine if the preloader has successfully loaded all of the images.
221
+ isResolved : function isResolved ( ) {
222
+
223
+ return ( this . state === this . states . RESOLVED ) ;
224
+
225
+ } ,
226
+
227
+
228
+ // I initiate the preload of the images. Returns a promise.
229
+ load : function load ( ) {
230
+
231
+ // If the images are already loading, return the existing promise.
232
+ if ( this . isInitiated ( ) ) {
233
+
234
+ return ( this . promise ) ;
235
+
236
+ }
237
+
238
+ this . state = this . states . LOADING ;
239
+
240
+ for ( var i = 0 ; i < this . imageCount ; i ++ ) {
241
+
242
+ this . loadImageLocation ( this . imageLocations [ i ] ) ;
243
+
244
+ }
245
+
246
+ // Return the deferred promise for the load event.
247
+ return ( this . promise ) ;
248
+
249
+ } ,
250
+
251
+
252
+ // ---
253
+ // PRIVATE METHODS.
254
+ // ---
255
+
256
+
257
+ // I handle the load-failure of the given image location.
258
+ handleImageError : function handleImageError ( imageLocation ) {
259
+
260
+ this . errorCount ++ ;
261
+
262
+ // If the preload action has already failed, ignore further action.
263
+ if ( this . isRejected ( ) ) {
264
+
265
+ return ;
266
+
267
+ }
268
+
269
+ this . state = this . states . REJECTED ;
270
+
271
+ this . deferred . reject ( imageLocation ) ;
272
+
273
+ } ,
274
+
275
+
276
+ // I handle the load-success of the given image location.
277
+ handleImageLoad : function handleImageLoad ( imageLocation ) {
278
+
279
+ this . loadCount ++ ;
280
+
281
+ // If the preload action has already failed, ignore further action.
282
+ if ( this . isRejected ( ) ) {
283
+
284
+ return ;
285
+
286
+ }
287
+
288
+ // Notify the progress of the overall deferred. This is different
289
+ // than Resolving the deferred - you can call notify many times
290
+ // before the ultimate resolution (or rejection) of the deferred.
291
+ this . deferred . notify ( {
292
+ percent : Math . ceil ( this . loadCount / this . imageCount * 100 ) ,
293
+ imageLocation : imageLocation
294
+ } ) ;
295
+
296
+ // If all of the images have loaded, we can resolve the deferred
297
+ // value that we returned to the calling context.
298
+ if ( this . loadCount === this . imageCount ) {
299
+
300
+ this . state = this . states . RESOLVED ;
301
+
302
+ this . deferred . resolve ( this . imageLocations ) ;
303
+
304
+ }
305
+
306
+ } ,
307
+
308
+
309
+ // I load the given image location and then wire the load / error
310
+ // events back into the preloader instance.
311
+ // --
312
+ // NOTE: The load/error events trigger a $digest.
313
+ loadImageLocation : function loadImageLocation ( imageLocation ) {
314
+
315
+ var preloader = this ;
316
+
317
+ // When it comes to creating the image object, it is critical that
318
+ // we bind the event handlers BEFORE we actually set the image
319
+ // source. Failure to do so will prevent the events from proper
320
+ // triggering in some browsers.
321
+ var image = $ ( new Image ( ) )
322
+ . load (
323
+ function ( event ) {
324
+
325
+ // Since the load event is asynchronous, we have to
326
+ // tell AngularJS that something changed.
327
+ $rootScope . $apply (
328
+ function ( ) {
329
+
330
+ preloader . handleImageLoad ( event . target . src ) ;
331
+
332
+ // Clean up object reference to help with the
333
+ // garbage collection in the closure.
334
+ preloader = image = event = null ;
335
+
336
+ }
337
+ ) ;
338
+
339
+ }
340
+ )
341
+ . error (
342
+ function ( event ) {
343
+
344
+ // Since the load event is asynchronous, we have to
345
+ // tell AngularJS that something changed.
346
+ $rootScope . $apply (
347
+ function ( ) {
348
+
349
+ preloader . handleImageError ( event . target . src ) ;
350
+
351
+ // Clean up object reference to help with the
352
+ // garbage collection in the closure.
353
+ preloader = image = event = null ;
354
+
355
+ }
356
+ ) ;
357
+
358
+ }
359
+ )
360
+ . prop ( "src" , imageLocation )
361
+ ;
362
+
363
+ }
364
+
365
+ } ;
366
+
367
+
368
+ // Return the factory instance.
369
+ return ( Preloader ) ;
370
+
371
+ }
372
+ ) ;
373
+
374
+ </ script >
375
+
376
+ </ body >
377
+ </ html >
0 commit comments