Skip to content

Commit 829a5b4

Browse files
committed
Add preloading images demo.
1 parent 4fef0cb commit 829a5b4

File tree

3 files changed

+380
-0
lines changed

3 files changed

+380
-0
lines changed
256 KB
Loading
+377
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
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>

index.htm

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ <h1>
1313
</h1>
1414

1515
<ul>
16+
<li>
17+
<a href="./demos/preloading-images-angularjs/">Preloading Images In AngularJS With Promises</a>
18+
</li>
1619
<li>
1720
<a href="./demos/case-study-scope-digest-angularjs/">Case Study: Using $scope.$digest() As A Performance Optimization In AngularJS</a>
1821
</li>

0 commit comments

Comments
 (0)