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