Skip to content

Commit

Permalink
docs(bootstrap-prettify): fix $timeout issues and update related docs
Browse files Browse the repository at this point in the history
End 2 end tests wait for all `$timeout`s to be run before completing the test.
This was problematic where we were using timeouts that restarted themselves because
there would never be a point when all timeouts had completed, causing the tests to hang.

To fix this $timeout had been monkey-patched but this caused other issue itself.

Now that we have $interval we don't need to use $timeout handlers that re-trigger the $timeout
so we can ditch the monkey-patch.

This commit tidies up any examples that are using this approach and changes them to use $interval
instead.

Closes angular#5232
  • Loading branch information
petebacondarwin committed Dec 17, 2013
1 parent cb29632 commit 73c6671
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 119 deletions.
12 changes: 1 addition & 11 deletions docs/components/angular-bootstrap/bootstrap-prettify.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,17 +215,7 @@ directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location',
}];
this.html5Mode = angular.noop;
});
$provide.decorator('$timeout', ['$rootScope', '$delegate', function($rootScope, $delegate) {
return angular.extend(function(fn, delay) {
if (delay && delay > 50) {
return setTimeout(function() {
$rootScope.$apply(fn);
}, delay);
} else {
return $delegate.apply(this, arguments);
}
}, $delegate);
}]);

$provide.decorator('$rootScope', ['$delegate', function($delegate) {
embedRootScope = $delegate;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,18 @@ of which depend on other services that are provided by the Angular framework:
* @param {*} message Message to be logged.
*/
function batchLogModule($provide){
$provide.factory('batchLog', ['$timeout', '$log', function($timeout, $log) {
$provide.factory('batchLog', ['$interval', '$log', function($interval, $log) {
var messageQueue = [];

function log() {
if (messageQueue.length) {
$log('batchLog messages: ', messageQueue);
messageQueue = [];
}
$timeout(log, 50000);
}

// start periodic checking
log();
$interval(log, 50000);

return function(message) {
messageQueue.push(message);
Expand All @@ -88,7 +87,7 @@ of which depend on other services that are provided by the Angular framework:

Things to notice in this example:

* The `batchLog` service depends on the built-in {@link api/ng.$timeout $timeout} and
* The `batchLog` service depends on the built-in {@link api/ng.$interval $interval} and
{@link api/ng.$log $log} services, and allows messages to be logged into the
`console.log` in batches.
* The `routeTemplateMonitor` service depends on the built-in {@link api/ngRoute.$route
Expand Down
26 changes: 11 additions & 15 deletions docs/content/guide/directive.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -528,16 +528,18 @@ where:
* `attrs` is an object with the normalized attribute names and their corresponding values.

In our `link` function, we want to update the displayed time once a second, or whenever a user
changes the time formatting string that our directive binds to. We also want to remove the timeout
if the directive is deleted so we don't introduce a memory leak.
changes the time formatting string that our directive binds to. We will use the `$interval` service
to call a handler on a regular basis. This is easier than using `$timeout` but also works better with
end 2 end testing, where we want to ensure that all $timeouts have completed before completing the test.
We also want to remove the `$interval` if the directive is deleted so we don't introduce a memory leak.

<example module="docsTimeDirective">
<file name="script.js">
angular.module('docsTimeDirective', [])
.controller('Ctrl2', function($scope) {
$scope.format = 'M/d/yy h:mm:ss a';
})
.directive('myCurrentTime', function($timeout, dateFilter) {
.directive('myCurrentTime', function($interval, dateFilter) {

function link(scope, element, attrs) {
var format,
Expand All @@ -552,20 +554,14 @@ if the directive is deleted so we don't introduce a memory leak.
updateTime();
});

function scheduleUpdate() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
scheduleUpdate(); // schedule the next update
}, 1000);
}

element.on('$destroy', function() {
$timeout.cancel(timeoutId);
$interval.cancel(timeoutId);
});

// start the UI update process.
scheduleUpdate();
// start the UI update process; save the timeoutId for canceling
timeoutId = $interval(function() {
updateTime(); // update DOM
}, 1000);
}

return {
Expand All @@ -583,7 +579,7 @@ if the directive is deleted so we don't introduce a memory leak.

There are a couple of things to note here.
Just like the `module.controller` API, the function argument in `module.directive` is dependency
injected. Because of this, we can use `$timeout` and `dateFilter` inside our directive's `link`
injected. Because of this, we can use `$interval` and `dateFilter` inside our directive's `link`
function.

We register an event `element.on('$destroy', ...)`. What fires this `$destroy` event?
Expand Down
4 changes: 2 additions & 2 deletions docs/content/guide/scope.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,8 @@ the `$digest` phase. This delay is desirable, since it coalesces multiple model
For mutations to be properly observed, you should make them only within the {@link
api/ng.$rootScope.Scope#methods_$apply scope.$apply()}. (Angular APIs do this
implicitly, so no extra `$apply` call is needed when doing synchronous work in controllers,
or asynchronous work with {@link api/ng.$http $http} or {@link
api/ng.$timeout $timeout} services.
or asynchronous work with {@link api/ng.$http $http}, {@link api/ng.$timeout $timeout}
or {@link api/ng.$interval $interval} services.

4. **Mutation observation**

Expand Down
82 changes: 82 additions & 0 deletions src/ng/interval.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,88 @@ function $IntervalProvider() {
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
* will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block.
* @returns {promise} A promise which will be notified on each iteration.
*
* @example
<doc:example module="time">
<doc:source>
<script>
function Ctrl2($scope,$interval) {
$scope.format = 'M/d/yy h:mm:ss a';
$scope.blood_1 = 100;
$scope.blood_2 = 120;
var stop;
$scope.fight = function() {
// Don't start a new fight if we are already fighting
if ( angular.isDefined(stop) ) return;
stop = $interval(function() {
if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
$scope.blood_1 = $scope.blood_1 - 3;
$scope.blood_2 = $scope.blood_2 - 4;
} else {
$interval.cancel(stop);
}
}, 100);
};
$scope.stopFight = function() {
$interval.cancel(stop);
stop = undefined;
};
$scope.resetFight = function() {
$scope.blood_1 = 100;
$scope.blood_2 = 120;
}
}
angular.module('time', [])
// Register the 'myCurrentTime' directive factory method.
// We inject $interval and dateFilter service since the factory method is DI.
.directive('myCurrentTime', function($interval, dateFilter) {
// return the directive link function. (compile function not needed)
return function(scope, element, attrs) {
var format, // date format
stopTime; // so that we can cancel the time updates
// used to update the UI
function updateTime() {
element.text(dateFilter(new Date(), format));
}
// watch the expression, and update the UI on change.
scope.$watch(attrs.myCurrentTime, function(value) {
format = value;
updateTime();
});
stopTime = $interval(updateTime, 1000);
// listen on DOM destroy (removal) event, and cancel the next UI update
// to prevent updating time ofter the DOM element was removed.
element.bind('$destroy', function() {
$interval.cancel(stopTime);
});
}
});
</script>
<div>
<div ng-controller="Ctrl2">
Date format: <input ng-model="format"> <hr/>
Current time is: <span my-current-time="format"></span>
<hr/>
Blood 1 : <font color='red'>{{blood_1}}</font>
Blood 2 : <font color='red'>{{blood_2}}</font>
<button type="button" data-ng-click="fight()">Fight</button>
<button type="button" data-ng-click="stopFight()">StopFight</button>
<button type="button" data-ng-click="resetFight()">resetFight</button>
</div>
</div>
</doc:source>
</doc:example>
*/
function interval(fn, delay, count, invokeApply) {
var setInterval = $window.setInterval,
Expand Down
87 changes: 0 additions & 87 deletions src/ng/timeout.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,93 +32,6 @@ function $TimeoutProvider() {
* @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
* promise will be resolved with is the return value of the `fn` function.
*
* @example
<doc:example module="time">
<doc:source>
<script>
function Ctrl2($scope,$timeout) {
$scope.format = 'M/d/yy h:mm:ss a';
$scope.blood_1 = 100;
$scope.blood_2 = 120;
var stop;
$scope.fight = function() {
stop = $timeout(function() {
if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
$scope.blood_1 = $scope.blood_1 - 3;
$scope.blood_2 = $scope.blood_2 - 4;
$scope.fight();
} else {
$timeout.cancel(stop);
}
}, 100);
};
$scope.stopFight = function() {
$timeout.cancel(stop);
};
$scope.resetFight = function() {
$scope.blood_1 = 100;
$scope.blood_2 = 120;
}
}
angular.module('time', [])
// Register the 'myCurrentTime' directive factory method.
// We inject $timeout and dateFilter service since the factory method is DI.
.directive('myCurrentTime', function($timeout, dateFilter) {
// return the directive link function. (compile function not needed)
return function(scope, element, attrs) {
var format, // date format
timeoutId; // timeoutId, so that we can cancel the time updates
// used to update the UI
function updateTime() {
element.text(dateFilter(new Date(), format));
}
// watch the expression, and update the UI on change.
scope.$watch(attrs.myCurrentTime, function(value) {
format = value;
updateTime();
});
// schedule update in one second
function updateLater() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
updateLater(); // schedule another update
}, 1000);
}
// listen on DOM destroy (removal) event, and cancel the next UI update
// to prevent updating time ofter the DOM element was removed.
element.bind('$destroy', function() {
$timeout.cancel(timeoutId);
});
updateLater(); // kick off the UI update process.
}
});
</script>
<div>
<div ng-controller="Ctrl2">
Date format: <input ng-model="format"> <hr/>
Current time is: <span my-current-time="format"></span>
<hr/>
Blood 1 : <font color='red'>{{blood_1}}</font>
Blood 2 : <font color='red'>{{blood_2}}</font>
<button type="button" data-ng-click="fight()">Fight</button>
<button type="button" data-ng-click="stopFight()">StopFight</button>
<button type="button" data-ng-click="resetFight()">resetFight</button>
</div>
</div>
</doc:source>
</doc:example>
*/
function timeout(fn, delay, invokeApply) {
var deferred = $q.defer(),
Expand Down

0 comments on commit 73c6671

Please sign in to comment.