Skip to content

Commit ad3cc16

Browse files
mheveryIgorMinar
authored andcommitted
feat($route): add events before/after route change
BREAKING CHANGE * removing `onChange` FEATURE * adding three events: $beforeRouteChange, $afterRouteChange, $routeReload
1 parent 08d09ec commit ad3cc16

File tree

10 files changed

+286
-132
lines changed

10 files changed

+286
-132
lines changed

Rakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ ANGULAR = [
2727
'src/service/log.js',
2828
'src/service/resource.js',
2929
'src/service/route.js',
30+
'src/service/routeParams.js',
3031
'src/service/window.js',
3132
'src/service/xhr.bulk.js',
3233
'src/service/xhr.cache.js',

jsTestDriver-jquery.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ load:
3030
- src/service/log.js
3131
- src/service/resource.js
3232
- src/service/route.js
33+
- src/service/routeParams.js
3334
- src/service/window.js
3435
- src/service/xhr.bulk.js
3536
- src/service/xhr.cache.js

jsTestDriver-perf.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ load:
2828
- src/service/log.js
2929
- src/service/resource.js
3030
- src/service/route.js
31+
- src/service/routeParams.js
3132
- src/service/updateView.js
3233
- src/service/window.js
3334
- src/service/xhr.bulk.js

jsTestDriver.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ load:
3030
- src/service/log.js
3131
- src/service/resource.js
3232
- src/service/route.js
33+
- src/service/routeParams.js
3334
- src/service/window.js
3435
- src/service/xhr.bulk.js
3536
- src/service/xhr.cache.js

src/angular-bootstrap.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
'service/log.js',
115115
'service/resource.js',
116116
'service/route.js',
117+
'service/routeParams.js',
117118
'service/window.js',
118119
'service/xhr.bulk.js',
119120
'service/xhr.cache.js',

src/service/route.js

Lines changed: 139 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* @ngdoc service
66
* @name angular.service.$route
77
* @requires $location
8+
* @requires $routeParams
89
*
910
* @property {Object} current Reference to the current route definition.
1011
* @property {Array.<Object>} routes Array of all configured routes.
@@ -14,7 +15,7 @@
1415
* definition. It is used for deep-linking URLs to controllers and views (HTML partials).
1516
*
1617
* The `$route` service is typically used in conjunction with {@link angular.widget.ng:view ng:view}
17-
* widget.
18+
* widget and the {@link angular.service.$routeParams $routeParams} service.
1819
*
1920
* @example
2021
This example shows how changing the URL hash causes the <tt>$route</tt>
@@ -24,23 +25,23 @@
2425
<doc:example>
2526
<doc:source jsfiddle="false">
2627
<script>
27-
function MainCntl($route, $location) {
28+
function MainCntl($route, $routeParams, $location) {
2829
this.$route = $route;
2930
this.$location = $location;
31+
this.$routeParams = $routeParams;
3032
3133
$route.when('/Book/:bookId', {template: 'examples/book.html', controller: BookCntl});
3234
$route.when('/Book/:bookId/ch/:chapterId', {template: 'examples/chapter.html', controller: ChapterCntl});
33-
$route.onChange(function() {
34-
$route.current.scope.params = $route.current.params;
35-
});
3635
}
3736
38-
function BookCntl() {
37+
function BookCntl($routeParams) {
3938
this.name = "BookCntl";
39+
this.params = $routeParams;
4040
}
4141
42-
function ChapterCntl() {
42+
function ChapterCntl($routeParams) {
4343
this.name = "ChapterCntl";
44+
this.params = $routeParams;
4445
}
4546
</script>
4647
@@ -54,6 +55,7 @@
5455
<pre>$route.current.template = {{$route.current.template}}</pre>
5556
<pre>$route.current.params = {{$route.current.params}}</pre>
5657
<pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
58+
<pre>$routeParams = {{$routeParams}}</pre>
5759
<hr />
5860
<ng:view></ng:view>
5961
</div>
@@ -62,34 +64,66 @@
6264
</doc:scenario>
6365
</doc:example>
6466
*/
65-
angularServiceInject('$route', function($location) {
67+
angularServiceInject('$route', function($location, $routeParams) {
68+
/**
69+
* @workInProgress
70+
* @ngdoc event
71+
* @name angular.service.$route#$beforeRouteChange
72+
* @eventOf angular.service.$route
73+
* @eventType Broadcast on root scope
74+
* @description
75+
* Broadcasted before a route change.
76+
*
77+
* @param {Route} next Future route information.
78+
* @param {Route} current Current route information.
79+
*
80+
* The `Route` object extends the route definition with the following properties.
81+
*
82+
* * `scope` - The instance of the route controller.
83+
* * `params` - The current {@link angular.service.$routeParams params}.
84+
*
85+
*/
86+
87+
/**
88+
* @workInProgress
89+
* @ngdoc event
90+
* @name angular.service.$route#$afterRouteChange
91+
* @eventOf angular.service.$route
92+
* @eventType Broadcast on root scope
93+
* @description
94+
* Broadcasted after a route change.
95+
*
96+
* @param {Route} current Current route information.
97+
* @param {Route} previous Previous route information.
98+
*
99+
* The `Route` object extends the route definition with the following properties.
100+
*
101+
* * `scope` - The instance of the route controller.
102+
* * `params` - The current {@link angular.service.$routeParams params}.
103+
*
104+
*/
105+
106+
/**
107+
* @workInProgress
108+
* @ngdoc event
109+
* @name angular.service.$route#$routeUpdate
110+
* @eventOf angular.service.$route
111+
* @eventType Emit on the current route scope.
112+
* @description
113+
*
114+
* The `reloadOnSearch` property has been set to false, and we are reusing the same
115+
* instance of the Controller.
116+
*/
117+
66118
var routes = {},
67-
onChange = [],
68119
matcher = switchRouteMatcher,
69120
parentScope = this,
121+
rootScope = this,
70122
dirty = 0,
71-
lastHashPath,
72-
lastRouteParams,
123+
allowReload = true,
73124
$route = {
74125
routes: routes,
75126

76-
/**
77-
* @workInProgress
78-
* @ngdoc method
79-
* @name angular.service.$route#onChange
80-
* @methodOf angular.service.$route
81-
*
82-
* @param {function()} fn Function that will be called when `$route.current` changes.
83-
* @returns {function()} The registered function.
84-
*
85-
* @description
86-
* Register a handler function that will be called when route changes
87-
*/
88-
onChange: function(fn) {
89-
onChange.push(fn);
90-
return fn;
91-
},
92-
93127
/**
94128
* @workInProgress
95129
* @ngdoc method
@@ -114,7 +148,7 @@ angularServiceInject('$route', function($location) {
114148
* @methodOf angular.service.$route
115149
*
116150
* @param {string} path Route path (matched against `$location.hash`)
117-
* @param {Object} params Mapping information to be assigned to `$route.current` on route
151+
* @param {Object} route Mapping information to be assigned to `$route.current` on route
118152
* match.
119153
*
120154
* Object properties:
@@ -139,14 +173,15 @@ angularServiceInject('$route', function($location) {
139173
* to update `$location.hash`.
140174
*
141175
* - `[reloadOnSearch=true]` - {boolean=} - reload route when $location.hashSearch
142-
* changes. If this option is disabled, you should set up a $watch to be notified of
143-
* param (hashSearch) changes as follows:
176+
* changes.
177+
*
178+
* If the option is set to false and url in the browser changes, then
179+
* $routeUpdate event is emited on the current route scope. You can use this event to
180+
* react to {@link angular.service.$routeParams} changes:
144181
*
145-
* function MyCtrl($route) {
146-
* this.$watch(function() {
147-
* return $route.current.params;
148-
* }, function(scope, params) {
149-
* //do stuff with params
182+
* function MyCtrl($route, $routeParams) {
183+
* this.$on('$routeUpdate', function() {
184+
* // do stuff with $routeParams
150185
* });
151186
* }
152187
*
@@ -155,13 +190,13 @@ angularServiceInject('$route', function($location) {
155190
* @description
156191
* Adds a new route definition to the `$route` service.
157192
*/
158-
when:function (path, params) {
193+
when:function (path, route) {
159194
if (isUndefined(path)) return routes; //TODO(im): remove - not needed!
160-
var route = routes[path];
161-
if (!route) route = routes[path] = {reloadOnSearch: true};
162-
if (params) extend(route, params); //TODO(im): what the heck? merge two route definitions?
195+
var routeDef = routes[path];
196+
if (!routeDef) routeDef = routes[path] = {reloadOnSearch: true};
197+
if (route) extend(routeDef, route); //TODO(im): what the heck? merge two route definitions?
163198
dirty++;
164-
return route;
199+
return routeDef;
165200
},
166201

167202
/**
@@ -192,10 +227,18 @@ angularServiceInject('$route', function($location) {
192227
*/
193228
reload: function() {
194229
dirty++;
230+
allowReload = false;
195231
}
196232
};
197233

198234

235+
236+
this.$watch(function(){ return dirty + $location.hash; }, updateRoute);
237+
238+
return $route;
239+
240+
/////////////////////////////////////////////////////
241+
199242
function switchRouteMatcher(on, when, dstName) {
200243
var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$',
201244
params = [],
@@ -219,79 +262,72 @@ angularServiceInject('$route', function($location) {
219262
return match ? dst : null;
220263
}
221264

222-
223265
function updateRoute(){
224-
var selectedRoute, pathParams, segmentMatch, key, redir;
266+
var next = parseRoute(),
267+
last = $route.current;
225268

226-
if ($route.current) {
227-
if (!$route.current.reloadOnSearch && (lastHashPath == $location.hashPath)) {
228-
$route.current.params = extend($location.hashSearch, lastRouteParams);
229-
return;
230-
}
231-
232-
if ($route.current.scope) {
233-
$route.current.scope.$destroy();
269+
if (next && last && next.$route === last.$route
270+
&& equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && allowReload) {
271+
$route.current = next;
272+
copy(next.params, $routeParams);
273+
last.scope && last.scope.$emit('$routeUpdate');
274+
} else {
275+
allowReload = true;
276+
rootScope.$broadcast('$beforeRouteChange', next, last);
277+
last && last.scope && last.scope.$destroy();
278+
$route.current = next;
279+
if (next) {
280+
if (next.redirectTo) {
281+
$location.update(isString(next.redirectTo)
282+
? {hashSearch: next.params, hashPath: interpolate(next.redirectTo, next.params)}
283+
: {hash: next.redirectTo(next.pathParams,
284+
$location.hash, $location.hashPath, $location.hashSearch)});
285+
} else {
286+
copy(next.params, $routeParams);
287+
next.scope = parentScope.$new(next.controller);
288+
}
234289
}
290+
rootScope.$broadcast('$afterRouteChange', next, last);
235291
}
292+
}
293+
236294

237-
lastHashPath = $location.hashPath;
238-
$route.current = null;
295+
/**
296+
* @returns the current active route, by matching it against the URL
297+
*/
298+
function parseRoute(){
239299
// Match a route
240-
forEach(routes, function(rParams, rPath) {
241-
if (!pathParams) {
242-
if ((pathParams = matcher($location.hashPath, rPath))) {
243-
selectedRoute = rParams;
244-
}
300+
var params, match;
301+
forEach(routes, function(route, path) {
302+
if (!match && (params = matcher($location.hashPath, path))) {
303+
match = inherit(route, {
304+
params: extend({}, $location.hashSearch, params),
305+
pathParams: params});
306+
match.$route = route;
245307
}
246308
});
247-
248309
// No route matched; fallback to "otherwise" route
249-
selectedRoute = selectedRoute || routes[null];
250-
251-
if(selectedRoute) {
252-
if (selectedRoute.redirectTo) {
253-
if (isString(selectedRoute.redirectTo)) {
254-
// interpolate the redirectTo string
255-
redir = {hashPath: '',
256-
hashSearch: extend({}, $location.hashSearch, pathParams)};
257-
258-
forEach(selectedRoute.redirectTo.split(':'), function(segment, i) {
259-
if (i==0) {
260-
redir.hashPath += segment;
261-
} else {
262-
segmentMatch = segment.match(/(\w+)(.*)/);
263-
key = segmentMatch[1];
264-
redir.hashPath += pathParams[key] || $location.hashSearch[key];
265-
redir.hashPath += segmentMatch[2] || '';
266-
delete redir.hashSearch[key];
267-
}
268-
});
269-
} else {
270-
// call custom redirectTo function
271-
redir = {hash: selectedRoute.redirectTo(pathParams, $location.hash, $location.hashPath,
272-
$location.hashSearch)};
273-
}
310+
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
311+
}
274312

275-
$location.update(redir);
276-
return;
313+
/**
314+
* @returns interpolation of the redirect path with the parametrs
315+
*/
316+
function interpolate(string, params) {
317+
var result = [];
318+
forEach((string||'').split(':'), function(segment, i) {
319+
if (i == 0) {
320+
result.push(segment);
321+
} else {
322+
var segmentMatch = segment.match(/(\w+)(.*)/);
323+
var key = segmentMatch[1];
324+
result.push(params[key]);
325+
result.push(segmentMatch[2] || '');
326+
delete params[key];
277327
}
278-
279-
$route.current = extend({}, selectedRoute);
280-
$route.current.params = extend({}, $location.hashSearch, pathParams);
281-
lastRouteParams = pathParams;
282-
}
283-
284-
//fire onChange callbacks
285-
forEach(onChange, parentScope.$eval, parentScope);
286-
287-
// Create the scope if we have matched a route
288-
if ($route.current) {
289-
$route.current.scope = parentScope.$new($route.current.controller);
290-
}
328+
});
329+
return result.join('');
291330
}
292331

293332

294-
this.$watch(function(){ return dirty + $location.hash; }, updateRoute);
295-
296-
return $route;
297-
}, ['$location']);
333+
}, ['$location', '$routeParams']);

0 commit comments

Comments
 (0)