| 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 |