OLD | NEW |
1 /** | 1 /** |
| 2 * Route configuration for single-page applications. |
| 3 * |
2 * The [routing] library makes it easier to build large single-page | 4 * The [routing] library makes it easier to build large single-page |
3 * applications. The library lets you map the browser address bar to semantic | 5 * applications. The library lets you map the browser address bar to semantic |
4 * structure of your application and keeps them in sync. | 6 * structure of your application and keeps them in sync. |
5 * | 7 * |
6 * Angular uses the [route_hierarchical] package to define application routes | 8 * Angular uses the [route_hierarchical] package to define application routes |
7 * and to provide custom tools to make it easier to use routing with Angular | 9 * and to provide custom tools to make it easier to use routing with Angular |
8 * templates. | 10 * templates. |
9 * | 11 * |
10 * Lets consider a simple recipe book application. The application might have | 12 * Let's consider a simple recipe book application. The application might have |
11 * the following pages: | 13 * the following pages: |
12 * | 14 * |
13 * * recipes list/search | 15 * * recipes list/search |
14 * * add new recipe | 16 * * add new recipe |
15 * * view recipe | 17 * * view recipe |
16 * * edit recipe | 18 * * edit recipe |
17 * | 19 * |
18 * Each of those pages can be represented by an address: | 20 * Each of those pages can be represented by an address: |
19 * | 21 * |
20 * * `/recipes` | 22 * * `/recipes` |
21 * * `/addRecipe` | 23 * * `/addRecipe` |
22 * * `/recipe/:recipeId/view` | 24 * * `/recipe/:recipeId/view` |
23 * * `/recipe/:recipeId/edit` | 25 * * `/recipe/:recipeId/edit` |
24 * | 26 * |
25 * | 27 * |
26 * Lets try to define those routes in Angular. To get started we need to | 28 * Let's try to define those routes in Angular. To get started we need to |
27 * provide an implementation of [RouteInitializerFn] function. | 29 * provide an implementation of [RouteInitializerFn] function. |
28 * | 30 * |
29 * void initRoutes(Router router, ViewFactory view) { | 31 * void initRoutes(Router router, RouteViewFactory view) { |
30 * // define routes here. | 32 * // define routes here. |
31 * } | 33 * } |
32 * | 34 * |
33 * var module = new Module() | 35 * var module = new Module() |
34 * ..factory(RouteInitializerFn, (_) => initRoutes); | 36 * ..value(RouteInitializerFn, initRoutes); |
35 * | 37 * |
36 * Lets see how we could define our routes using the routing framework: | 38 * Let's see how we could define our routes using the routing framework: |
37 * | 39 * |
38 * void initRoutes(Router router, ViewFactory view) { | 40 * void initRoutes(Router router, RouteViewFactory view) { |
39 * router | 41 * router.root |
40 * ..addRoute( | 42 * ..addRoute( |
41 * name: 'recipes', | 43 * name: 'recipes', |
42 * path: '/recipes', | 44 * path: '/recipes', |
43 * enter: view('recipes.html')) | 45 * enter: view('recipes.html')) |
44 * ..addRoute( | 46 * ..addRoute( |
45 * name: 'addRecipe', | 47 * name: 'addRecipe', |
46 * path: '/addRecipe', | 48 * path: '/addRecipe', |
47 * enter: view('addRecipe.html')) | 49 * enter: view('addRecipe.html')) |
48 * ..addRoute( | 50 * ..addRoute( |
49 * name: 'viewRecipe', | 51 * name: 'viewRecipe', |
50 * path: '/recipe/:recipeId/view', | 52 * path: '/recipe/:recipeId/view', |
51 * enter: view('viewRecipe.html')) | 53 * enter: view('viewRecipe.html')) |
52 * ..addRoute( | 54 * ..addRoute( |
53 * name: 'editRecipe', | 55 * name: 'editRecipe', |
54 * path: '/recipe/:recipeId/edit', | 56 * path: '/recipe/:recipeId/edit', |
55 * enter: view('editRecipe.html')); | 57 * enter: view('editRecipe.html')); |
56 * } | 58 * } |
57 * | 59 * |
58 * We defined 4 routes and for each route we set views (templates) to be | 60 * We defined 4 routes and for each route we set views (templates) to be |
59 * displayed when that route is "entered". For example, when the browser URL | 61 * displayed when that route is "entered". For example, when the browser URL |
60 * is set to `/recipes`, the `recipes.html` will be displayed. | 62 * is set to `/recipes`, the `recipes.html` will be displayed. |
61 * | 63 * |
62 * You have to tell Angular where to load views by putting `<ng-view>` tag in | 64 * You have to tell Angular where to load views by putting `<ng-view>` tag in |
63 * you template. | 65 * you template. |
64 * | 66 * |
65 * Notice that `viewRecipe` and `editRecipe` route paths have `recipeId` | 67 * Notice that `viewRecipe` and `editRecipe` route paths have `recipeId` |
66 * parameter in them. We need to be able to get hold of that parameter in | 68 * parameter in them. We need to be able to get hold of that parameter in |
67 * order to know which recipe to load. Lets consider the following | 69 * order to know which recipe to load. Let's consider the following |
68 * `viewRecipe.html`. | 70 * `viewRecipe.html`. |
69 * | 71 * |
70 * <view-recipe></view-recipe> | 72 * <view-recipe></view-recipe> |
71 * | 73 * |
72 * The template contains a custom `view-recipe` component that handles | 74 * The template contains a custom `view-recipe` component that handles |
73 * displaying the recipe. Now, our `view-recipe` can inject [RouteProvider] | 75 * displaying the recipe. Now, our `view-recipe` can inject [RouteProvider] |
74 * to get hold of the route and its parameters. It might look like this: | 76 * to get hold of the route and its parameters. It might look like this: |
75 * | 77 * |
76 * @NgComponent(...) | 78 * @Component(...) |
77 * class ViewRecipeComponent { | 79 * class ViewRecipe { |
78 * ViewRecipeComponent(RouteProvider routeProvider) { | 80 * ViewRecipe(RouteProvider routeProvider) { |
79 * String recipeId = routeProvider.parameters['recipeId']; | 81 * String recipeId = routeProvider.parameters['recipeId']; |
80 * _loadRecipe(recipeId); | 82 * _loadRecipe(recipeId); |
81 * } | 83 * } |
82 * } | 84 * } |
83 * | 85 * |
84 * [RouteProvider] and [Route] can be used to control navigation, specifically, | 86 * [RouteProvider] and [Route] can be used to control navigation, specifically, |
85 * leaving of the route. For example, lets consider "edit recipe" component: | 87 * leaving of the route. For example, let's consider "edit recipe" component: |
86 * | 88 * |
87 * @NgComponent(...) | 89 * @Component(...) |
88 * class EditRecipeComponent implements NgDetachAware { | 90 * class EditRecipe implements DetachAware { |
89 * RouteHandle route; | 91 * RouteHandle route; |
90 * EditRecipeComponent(RouteProvider routeProvider) { | 92 * EditRecipe(RouteProvider routeProvider) { |
91 * RouteHandle route = routeProvider.route.newHandle(); | 93 * RouteHandle route = routeProvider.route.newHandle(); |
92 * _loadRecipe(route); | 94 * _loadRecipe(route); |
93 * route.onLeave.listen((RouteEvent event) { | 95 * route.onLeave.listen((RouteEvent event) { |
94 * event.allowLeave(_checkIfOkToLeave()); | 96 * event.allowLeave(_checkIfOkToLeave()); |
95 * }); | 97 * }); |
96 * } | 98 * } |
97 * | 99 * |
98 * /// Check if the editor has unsaved contents and if necessary ask | 100 * /// Check if the editor has unsaved contents and if necessary ask |
99 * /// the user if OK to leave this page. | 101 * /// the user if OK to leave this page. |
100 * Future<bool> _checkIfOkToLeave() {/* ... */} | 102 * Future<bool> _checkIfOkToLeave() {/* ... */} |
101 * | 103 * |
102 * detach() { | 104 * detach() { |
103 * route.discard(); | 105 * route.discard(); |
104 * } | 106 * } |
105 * } | 107 * } |
106 * | 108 * |
107 * [Route.onLeave] event is triggered when the browser is routed from an | 109 * [Route.onLeave] event is triggered when the browser is routed from an |
108 * active route to a different route. The active route can delay and | 110 * active route to a different route. The active route can delay and |
109 * potentially veto the navigation by passing a [Future<bool>] to | 111 * potentially veto the navigation by passing a [Future<bool>] to |
110 * [RouteEvent.allowLeave]. | 112 * [RouteEvent.allowLeave]. |
111 * | 113 * |
112 * Notice that we create a [RouteHandle] for our route. [RouteHandle] are | 114 * Notice that we create a [RouteHandle] for our route. [RouteHandle] are |
113 * a convinient wrapper around [Route] that makes unsubscribing route events | 115 * a convinient wrapper around [Route] that makes unsubscribing route events |
114 * easier. For example, notice that we didn't need to manually call | 116 * easier. For example, notice that we didn't need to manually call |
115 * [StreamSubscription.cancel] for subscription to [Route.onLeave]. Calling | 117 * [StreamSubscription.cancel] for subscription to [Route.onLeave]. Calling |
116 * [RouteHandle.discard] unsubscribes all listeneters created for the handle. | 118 * [RouteHandle.discard] unsubscribes all listeneters created for the handle. |
117 * | 119 * |
118 * | 120 * |
119 * # Hierarchical Routes | 121 * ## Hierarchical Routes |
120 * | 122 * |
121 * The routing framework allows us to define trees of routes. In our recipes | 123 * The routing framework allows us to define trees of routes. In our recipes |
122 * example we could have defined our routes like this: | 124 * example we could have defined our routes like this: |
123 * | 125 * |
124 * void initRoutes(Router router, ViewFactory view) { | 126 * void initRoutes(Router router, RouteViewFactory view) { |
125 * router | 127 * router.root |
126 * ..addRoute( | 128 * ..addRoute( |
127 * name: 'recipes', | 129 * name: 'recipes', |
128 * path: '/recipes', | 130 * path: '/recipes', |
129 * enter: view('recipes.html')) | 131 * enter: view('recipes.html')) |
130 * ..addRoute( | 132 * ..addRoute( |
131 * name: 'addRecipe', | 133 * name: 'addRecipe', |
132 * path: '/addRecipe', | 134 * path: '/addRecipe', |
133 * enter: view('addRecipe.html')) | 135 * enter: view('addRecipe.html')) |
134 * ..addRoute( | 136 * ..addRoute( |
135 * name: 'recipe', | 137 * name: 'recipe', |
136 * path: '/recipe/:recipeId', | 138 * path: '/recipe/:recipeId', |
137 * mount: (Route route) => route | 139 * mount: (Route route) => route |
138 * ..addRoute( | 140 * ..addRoute( |
139 * name: 'view', | 141 * name: 'view', |
140 * path: '/view', | 142 * path: '/view', |
141 * enter: view('viewRecipe.html')) | 143 * enter: view('viewRecipe.html')) |
142 * ..addRoute( | 144 * ..addRoute( |
143 * name: 'edit', | 145 * name: 'edit', |
144 * path: '/edit', | 146 * path: '/edit', |
145 * enter: view('editRecipe.html'))); | 147 * enter: view('editRecipe.html'))); |
146 * } | 148 * } |
147 * } | 149 * |
148 */ | 150 */ |
149 library angular.routing; | 151 library angular.routing; |
150 | 152 |
151 import 'dart:async'; | 153 import 'dart:async'; |
152 import 'dart:html'; | 154 import 'dart:html'; |
153 | 155 |
154 import 'package:di/di.dart'; | 156 import 'package:di/di.dart'; |
155 import 'package:angular/angular.dart'; | 157 import 'package:angular/application.dart'; |
| 158 import 'package:angular/core/annotation_src.dart'; |
| 159 import 'package:angular/core/module_internal.dart'; |
| 160 import 'package:angular/core_dom/module_internal.dart'; |
156 import 'package:route_hierarchical/client.dart'; | 161 import 'package:route_hierarchical/client.dart'; |
157 export 'package:route_hierarchical/client.dart'; | |
158 | 162 |
159 part 'routing.dart'; | 163 part 'routing.dart'; |
160 part 'ng_view.dart'; | 164 part 'ng_view.dart'; |
161 part 'ng_bind_route.dart'; | 165 part 'ng_bind_route.dart'; |
162 | 166 |
163 class NgRoutingModule extends Module { | 167 class RoutingModule extends Module { |
164 NgRoutingModule({bool usePushState: true}) { | 168 RoutingModule({bool usePushState: true}) { |
165 type(NgRoutingUsePushState); | 169 type(NgRoutingUsePushState); |
166 factory(Router, (injector) { | 170 factory(Router, (injector) { |
167 var useFragment = !injector.get(NgRoutingUsePushState).usePushState; | 171 var useFragment = !injector.get(NgRoutingUsePushState).usePushState; |
168 return new Router(useFragment: useFragment, | 172 return new Router(useFragment: useFragment, |
169 windowImpl: injector.get(Window)); | 173 windowImpl: injector.get(Window)); |
170 }); | 174 }); |
171 type(NgRoutingHelper); | 175 type(NgRoutingHelper); |
172 value(RouteProvider, null); | 176 value(RouteProvider, null); |
173 value(RouteInitializer, null); | 177 value(RouteInitializer, null); |
174 value(RouteInitializerFn, null); | 178 value(RouteInitializerFn, null); |
175 | 179 |
176 // directives | 180 // directives |
177 value(NgViewDirective, null); | 181 value(NgView, null); |
178 type(NgBindRouteDirective); | 182 type(NgBindRoute); |
179 } | 183 } |
180 } | 184 } |
181 | 185 |
182 /** | 186 /** |
183 * Allows configuration of [Router.useFragment]. By default [usePushState] is | 187 * Allows configuration of [Router.useFragment]. By default [usePushState] is |
184 * true, so the router will listen to [Window.onPopState] and route URLs like | 188 * true, so the router will listen to [Window.onPopState] and route URLs like |
185 * "http://host:port/foo/bar?baz=qux". Both the path and query parts of the URL | 189 * "http://host:port/foo/bar?baz=qux". Both the path and query parts of the URL |
186 * are used by the router. If [usePushState] is false, router will listen to | 190 * are used by the router. If [usePushState] is false, router will listen to |
187 * [Window.onHashChange] and route URLs like | 191 * [Window.onHashChange] and route URLs like |
188 * "http://host:port/path#/foo/bar?baz=qux". Everything after hash (#) is used | 192 * "http://host:port/path#/foo/bar?baz=qux". Everything after hash (#) is used |
189 * by the router. | 193 * by the router. |
190 */ | 194 */ |
191 @NgInjectableService() | 195 @Injectable() |
192 class NgRoutingUsePushState { | 196 class NgRoutingUsePushState { |
193 final bool usePushState; | 197 final bool usePushState; |
194 NgRoutingUsePushState(): usePushState = true; | 198 NgRoutingUsePushState(): usePushState = true; |
195 NgRoutingUsePushState.value(this.usePushState); | 199 NgRoutingUsePushState.value(this.usePushState); |
196 } | 200 } |
OLD | NEW |