Skip to content

Instantly share code, notes, and snippets.

@phplaw
Forked from bennadel/ng-repeat-complete.htm
Created April 12, 2018 11:26
Show Gist options
  • Save phplaw/b1ad49f43592c1c8e16b359f6f698e6a to your computer and use it in GitHub Desktop.
Save phplaw/b1ad49f43592c1c8e16b359f6f698e6a to your computer and use it in GitHub Desktop.
Hooking Into The Complete Event Of An ngRepeat Loop In AngularJS
<!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