-
-
Save phplaw/b1ad49f43592c1c8e16b359f6f698e6a to your computer and use it in GitHub Desktop.
Hooking Into The Complete Event Of An ngRepeat Loop In AngularJS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html ng-app="Demo"> | |
<head> | |
<meta charset="utf-8" /> | |
<title> | |
Hooking Into The ngRepeat Completion Event In AngularJS | |
</title> | |
<style type="text/css"> | |
a[ ng-click ] { | |
cursor: pointer ; | |
text-decoration: underline ; | |
} | |
</style> | |
</head> | |
<body ng-controller="AppController"> | |
<h1> | |
Hooking Into The ngRepeat Completion Event In AngularJS | |
</h1> | |
<h3> | |
Friends | |
</h3> | |
<ul> | |
<!-- | |
When the ngRepeat finishes its first round of rendering, | |
we're going to invoke the doSomething() callback. | |
--> | |
<li | |
ng-repeat="friend in friends" | |
repeat-complete="doSomething( $index )"> | |
{{ friend.name }} | |
</li> | |
</ul> | |
<p> | |
<a ng-click="addFriend()">Add Friend</a> | |
- | |
<em>This will NOT trigger any more "complete" events</em>. | |
</p> | |
<h3> | |
Enemies | |
</h3> | |
<ul> | |
<!-- | |
When the ngRepeat finishes its first round of rendering, | |
we're going to invoke the doSomething() callback. | |
--> | |
<li | |
ng-repeat="enemy in enemies" | |
repeat-complete="doSomething( $index )"> | |
{{ enemy.name }} | |
</li> | |
</ul> | |
<p> | |
<a ng-click="addEnemy()">Add Enemy</a> | |
- | |
<em>This WILL trigger one more "complete" event</em>. | |
</p> | |
<!-- Load scripts. --> | |
<script type="text/javascript" src="../../vendor/jquery/jquery-2.0.3.min.js"></script> | |
<script type="text/javascript" src="../../vendor/angularjs/angular-1.2.min.js"></script> | |
<script type="text/javascript"> | |
// Create an application module for our demo. | |
var app = angular.module( "Demo", [] ); | |
// -------------------------------------------------- // | |
// -------------------------------------------------- // | |
// I control the root of the application. | |
app.controller( | |
"AppController", | |
function( $scope ) { | |
// Define the collection of friends to render. | |
$scope.friends = [ | |
{ | |
name: "Sarah" | |
}, | |
{ | |
name: "Joanna" | |
}, | |
{ | |
name: "Tricia" | |
} | |
]; | |
// Define the collection of enemies to render. This | |
// collection will start out empty in order to demonstrate | |
// that this approach requires at least ONE element to | |
// render. | |
$scope.enemies = []; | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I add a new enemty to the collection. | |
$scope.addEnemy = function() { | |
$scope.enemies.push({ | |
name: "Banana" | |
}); | |
} | |
// I add a new friend to the collection. | |
$scope.addFriend = function() { | |
$scope.friends.push({ | |
name: "Anna" | |
}); | |
} | |
// I am the callback handler for the ngRepeat completion. | |
$scope.doSomething = function( index ) { | |
console.log( "ngRepeat completed (" + index + ")!" ); | |
}; | |
} | |
); | |
// -------------------------------------------------- // | |
// -------------------------------------------------- // | |
// I invoke the given expression when associated ngRepeat loop | |
// has finished its first round of rendering. | |
app.directive( | |
"repeatComplete", | |
function( $rootScope ) { | |
// Because we can have multiple ng-repeat directives in | |
// the same container, we need a way to differentiate | |
// the different sets of elements. We'll add a unique ID | |
// to each set. | |
var uuid = 0; | |
// I compile the DOM node before it is linked by the | |
// ng-repeat directive. | |
function compile( tElement, tAttributes ) { | |
// Get the unique ID that we'll be using for this | |
// particular instance of the directive. | |
var id = ++uuid; | |
// Add the unique ID so we know how to query for | |
// DOM elements during the digests. | |
tElement.attr( "repeat-complete-id", id ); | |
// Since this directive doesn't have a linking phase, | |
// remove it from the DOM node. | |
tElement.removeAttr( "repeat-complete" ); | |
// Keep track of the expression we're going to | |
// invoke once the ng-repeat has finished | |
// rendering. | |
var completeExpression = tAttributes.repeatComplete; | |
// Get the element that contains the list. We'll | |
// use this element as the launch point for our | |
// DOM search query. | |
var parent = tElement.parent(); | |
// Get the scope associated with the parent - we | |
// want to get as close to the ngRepeat so that our | |
// watcher will automatically unbind as soon as the | |
// parent scope is destroyed. | |
var parentScope = ( parent.scope() || $rootScope ); | |
// Since we are outside of the ng-repeat directive, | |
// we'll have to check the state of the DOM during | |
// each $digest phase; BUT, we only need to do this | |
// once, so save a referene to the un-watcher. | |
var unbindWatcher = parentScope.$watch( | |
function() { | |
console.info( "Digest running." ); | |
// Now that we're in a digest, check to see | |
// if there are any ngRepeat items being | |
// rendered. Since we want to know when the | |
// list has completed, we only need the last | |
// one we can find. | |
var lastItem = parent.children( "*[ repeat-complete-id = '" + id + "' ]:last" ); | |
// If no items have been rendered yet, stop. | |
if ( ! lastItem.length ) { | |
return; | |
} | |
// Get the local ng-repeat scope for the item. | |
var itemScope = lastItem.scope(); | |
// If the item is the "last" item as defined | |
// by the ng-repeat directive, then we know | |
// that the ng-repeat directive has finished | |
// rendering its list (for the first time). | |
if ( itemScope.$last ) { | |
// Stop watching for changes - we only | |
// care about the first complete rendering. | |
unbindWatcher(); | |
// Invoke the callback. | |
itemScope.$eval( completeExpression ); | |
} | |
} | |
); | |
} | |
// Return the directive configuration. It's important | |
// that this compiles before the ngRepeat directive | |
// compiles the DOM node. | |
return({ | |
compile: compile, | |
priority: 1001, | |
restrict: "A" | |
}); | |
} | |
); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment