From c7e719bb071858578f0e432dde02ecf46220bd7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Ram=C3=B3n=20L=C3=B3pez?= <lrlopez@gmail.com> Date: Sat, 13 Apr 2013 19:37:11 +0200 Subject: [PATCH] feat($location): Location change notifications can now be skipped This adds a new method `notify` to `$location` that allows updating the location without triggering `$locationChangeStart`/`$locationChangeSuccess` events. The method is chainable and must be called with a boolean parameter. Any falsy value will disable the notification procedure and will block any route update that may apply. The skip flag will be reset after any digest cycle, so it must be set again on any subsequent change if needed. Example: `$location.path('/client/2').replace().notify(false);` Closes #1699 --- .../guide/dev_guide.services.$location.ngdoc | 3 ++ src/ng/location.js | 39 +++++++++++++-- test/ng/locationSpec.js | 47 +++++++++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/docs/content/guide/dev_guide.services.$location.ngdoc b/docs/content/guide/dev_guide.services.$location.ngdoc index 788fe9a73000..cac8c04c573b 100644 --- a/docs/content/guide/dev_guide.services.$location.ngdoc +++ b/docs/content/guide/dev_guide.services.$location.ngdoc @@ -141,6 +141,9 @@ rather than an addition to the browser history. Once the browser is updated, the resets the flag set by `replace()` method and future mutations will create new history records, unless `replace()` is called again. +Also, you may want to update the location without broadcasting a notification in order to avoid +triggering a route update. This can be accomplished calling `notify(false)` within the chain. + ### Setters and character encoding You can pass special characters to `$location` service and it will encode them according to rules specified in {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. When you access the methods: diff --git a/src/ng/location.js b/src/ng/location.js index 3196b1d57b7c..53cb16bd4cee 100644 --- a/src/ng/location.js +++ b/src/ng/location.js @@ -224,6 +224,12 @@ LocationUrl.prototype = { */ $$replace: false, + /** + * Broadcast a location change event the next time it changes + * @private + */ + $$notify: true, + /** * @ngdoc method * @name ng.$location#absUrl @@ -395,6 +401,22 @@ LocationUrl.prototype = { replace: function() { this.$$replace = true; return this; + }, + + /** + * @ngdoc method + * @name ng.$location#notify + * @methodOf ng.$location + * + * @description + * Allows to skip the location change broadcast event next time the location changes + * when called with a false parameter + * + * @param {boolean=} notify Set to false to disable the location change event once + */ + notify: function(notify) { + this.$$notify = notify; + return this; } }; @@ -592,20 +614,27 @@ function $LocationProvider(){ $rootScope.$watch(function $locationWatch() { var oldUrl = $browser.url(); var currentReplace = $location.$$replace; + var currentNotify = $location.$$notify; if (!changeCounter || oldUrl != $location.absUrl()) { changeCounter++; $rootScope.$evalAsync(function() { - if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). - defaultPrevented) { - $location.$$parse(oldUrl); - } else { + if (currentNotify == true) { + if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). + defaultPrevented) { + $location.$$parse(oldUrl); + } else { + $browser.url($location.absUrl(), currentReplace); + afterLocationChange(oldUrl); + } + } + else { $browser.url($location.absUrl(), currentReplace); - afterLocationChange(oldUrl); } }); } $location.$$replace = false; + $location.$$notify = true; return changeCounter; }); diff --git a/test/ng/locationSpec.js b/test/ng/locationSpec.js index bf91c25023ca..b3a162aa9c23 100644 --- a/test/ng/locationSpec.js +++ b/test/ng/locationSpec.js @@ -131,6 +131,15 @@ describe('$location', function() { }); + it('notify should set $$notify flag and return itself', function() { + expect(url.$$notify).toBe(true); + + url.notify(false); + expect(url.$$notify).toBe(false); + expect(url.notify(true)).toBe(url); + }); + + it('should parse new url', function() { url = new LocationUrl('http://host.com/base'); expect(url.path()).toBe('/base'); @@ -451,6 +460,32 @@ describe('$location', function() { })); + it('should skip location change notifications when notify is set to false', inject( + function($log, $location, $browser, $rootScope) { + + $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { + $log.info('before', newUrl, oldUrl, $browser.url()); + }); + $rootScope.$apply(); // initial $locationChangeStart + expect($log.info.logs.shift()). + toEqual(['before', 'http://new.com/a/b', 'http://new.com/a/b', 'http://new.com/a/b']); + + // Setting notify to false shouldn't trigger $locationChangeStart + $location.path('/any').notify(false); + $rootScope.$apply(); + expect($log.info.logs).toEqual([]); + expect($browser.url()).toEqual('http://new.com/a/b#!/any'); + + // Setting notify to true should trigger $locationChangeStart + $location.path('/anyother').notify(true); + $rootScope.$apply(); + expect($browser.url()).toEqual('http://new.com/a/b#!/anyother'); + expect($log.info.logs.shift()). + toEqual(['before', 'http://new.com/a/b#!/anyother', 'http://new.com/a/b#!/any', 'http://new.com/a/b#!/any']); + }) + ); + + it('should always reset replace flag after running watch', inject(function($rootScope, $location) { // init watches $location.url('/initUrl'); @@ -473,6 +508,18 @@ describe('$location', function() { })); + it('should always reset notify flag after running watch', inject(function($rootScope, $location) { + // flag gets reset after digest + $location.url('/newUrl').notify(false); + $rootScope.$apply(); + expect($location.$$notify).toBe(true); + + // even if no changes on location are stated + $location.notify(false); + $rootScope.$apply(); + expect($location.$$notify).toBe(true); + })); + it('should update the browser if changed from within a watcher', inject(function($rootScope, $location, $browser) { $rootScope.$watch(function() { return true; }, function() { $location.path('/changed');