| Index: third_party/pkg/angular/lib/routing/module.dart
|
| diff --git a/third_party/pkg/angular/lib/routing/module.dart b/third_party/pkg/angular/lib/routing/module.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6e14146ea3d8840c39a92c011301e4f501d29c3d
|
| --- /dev/null
|
| +++ b/third_party/pkg/angular/lib/routing/module.dart
|
| @@ -0,0 +1,199 @@
|
| +/**
|
| + * Routing library makes it easier to build large single-page application. The
|
| + * library lets you map the browser address bar to semantic structure of your
|
| + * application and keeps them in sync.
|
| + *
|
| + * Angular uses [route_hierarchical] package to define application routes and
|
| + * provides custom tools that it easier to use routing with Angular templates.
|
| + *
|
| + * Lets consider a simple recipe book application. The application might have
|
| + * the following pages:
|
| + *
|
| + * * recipes list/search
|
| + * * add new recipe
|
| + * * view recipe
|
| + * * edit recipe
|
| + *
|
| + * Each of those pages can be represented by an address:
|
| + *
|
| + * * `/recipes`
|
| + * * `/addRecipe`
|
| + * * `/recipe/:recipeId/view`
|
| + * * `/recipe/:recipeId/edit`
|
| + *
|
| + *
|
| + * Lets try to define those routes in Angular. To get started we need to
|
| + * provide an implementation of [RouteInitializer] interface.
|
| + *
|
| + * class RecipesRouteInitializer implements RouteInitializer {
|
| + * void init(Router router, ViewFactory view) {
|
| + * // define routes here.
|
| + * }
|
| + * }
|
| + *
|
| + * var module = new Module()
|
| + * ..type(RouteInitializer, implementedBy: RecipesRouteInitializer);
|
| + *
|
| + * Lets see how we could define our routes using the routing framework:
|
| + *
|
| + * class RecipesRouteInitializer implements RouteInitializer {
|
| + * void init(Router router, ViewFactory view) {
|
| + * router
|
| + * ..addRoute(
|
| + * name: 'recipes',
|
| + * path: '/recipes',
|
| + * enter: view('recipes.html'))
|
| + * ..addRoute(
|
| + * name: 'addRecipe',
|
| + * path: '/addRecipe',
|
| + * enter: view('addRecipe.html'))
|
| + * ..addRoute(
|
| + * name: 'viewRecipe',
|
| + * path: '/recipe/:recipeId/view',
|
| + * enter: view('viewRecipe.html'))
|
| + * ..addRoute(
|
| + * name: 'editRecipe',
|
| + * path: '/recipe/:recipeId/edit',
|
| + * enter: view('editRecipe.html'));
|
| + * }
|
| + * }
|
| + *
|
| + * We defined 4 routes and for each route we set views (templates) to be
|
| + * displayed when that route is "entered". For example, when the browser URL
|
| + * is set to `/recipes`, the `recipes.html` will be displayed.
|
| + *
|
| + * You have to tell Angular where to load views by putting `<ng-view>` tag in
|
| + * you template.
|
| + *
|
| + * Notice that `viewRecipe` and `editRecipe` route paths have `recipeId`
|
| + * parameter in them. We need to be able to get hold of that parameter in
|
| + * order to know which recipe to load. Lets consider the following
|
| + * `viewRecipe.html`.
|
| + *
|
| + * <view-recipe></view-recipe>
|
| + *
|
| + * The template contains a custom `view-recipe` component that handles the
|
| + * displaying of the recipe. Now, our `view-recipe` can inject [RouteProvider]
|
| + * to get hold of the route and its parameters. It might look like this:
|
| + *
|
| + * @NgComponent(...)
|
| + * class ViewRecipeComponent {
|
| + * ViewRecipeComponent(RouteProvider routeProvider) {
|
| + * String recipeId = routeProvider.parameters['recipeId'];
|
| + * _loadRecipe(recipeId);
|
| + * }
|
| + * }
|
| + *
|
| + * [RouteProvider] and [Route] can be used to control navigation, specifically,
|
| + * leaving of the route. For example, lets consider "edit recipe" component:
|
| + *
|
| + * @NgComponent(...)
|
| + * class EditRecipeComponent implements NgDetachAware {
|
| + * RouteHandle route;
|
| + * EditRecipeComponent(RouteProvider routeProvider) {
|
| + * RouteHandle route = routeProvider.route.newHandle();
|
| + * _loadRecipe(route);
|
| + * route.onLeave.listen((RouteEvent event) {
|
| + * event.allowLeave(_checkIfOkToLeave());
|
| + * });
|
| + * }
|
| + *
|
| + * /// Check if the editor has unsaved contents and if necessary ask
|
| + * /// the user if OK to leave this page.
|
| + * Future<bool> _checkIfOkToLeave() {/* ... */}
|
| + *
|
| + * detach() {
|
| + * route.discard();
|
| + * }
|
| + * }
|
| + *
|
| + * [Route.onLeave] event is triggered when the browser is routed from an
|
| + * active route to a different route. The active route can delay and
|
| + * potentially veto the navigation by passing a [Future<bool>] to
|
| + * [RouteEvent.allowLeave].
|
| + *
|
| + * Notice that we create a [RouteHandle] for our route. [RouteHandle] are
|
| + * a convinient wrapper around [Route] that makes unsubscribing route events
|
| + * easier. For example, notice that we didn't need to manually call
|
| + * [StreamSubscription.cancel] for subscription to [Route.onLeave]. Calling
|
| + * [RouteHandle.discard] unsubscribes all listeneters created for the handle.
|
| + *
|
| + *
|
| + * # Hierarchical Routes
|
| + *
|
| + * The routing framework allows us to define trees of routes. In our recipes
|
| + * example we could have defined our routes like this:
|
| + *
|
| + * class RecipesRouteInitializer implements RouteInitializer {
|
| + * void init(Router router, ViewFactory view) {
|
| + * router
|
| + * ..addRoute(
|
| + * name: 'recipes',
|
| + * path: '/recipes',
|
| + * enter: view('recipes.html'))
|
| + * ..addRoute(
|
| + * name: 'addRecipe',
|
| + * path: '/addRecipe',
|
| + * enter: view('addRecipe.html'))
|
| + * ..addRoute(
|
| + * name: 'recipe',
|
| + * path: '/recipe/:recipeId',
|
| + * mount: (Route route) => route
|
| + * ..addRoute(
|
| + * name: 'view',
|
| + * path: '/view',
|
| + * enter: view('viewRecipe.html'))
|
| + * ..addRoute(
|
| + * name: 'edit',
|
| + * path: '/edit',
|
| + * enter: view('editRecipe.html')));
|
| + * }
|
| + * }
|
| + */
|
| +library angular.routing;
|
| +
|
| +import 'dart:async';
|
| +import 'dart:html';
|
| +
|
| +import 'package:di/di.dart';
|
| +import 'package:angular/angular.dart';
|
| +import 'package:route_hierarchical/client.dart';
|
| +export 'package:route_hierarchical/client.dart';
|
| +
|
| +part 'routing.dart';
|
| +part 'ng_view.dart';
|
| +part 'ng_bind_route.dart';
|
| +
|
| +class NgRoutingModule extends Module {
|
| + NgRoutingModule({bool usePushState: true}) {
|
| + type(NgRoutingUsePushState);
|
| + factory(Router, (injector) {
|
| + var useFragment = !injector.get(NgRoutingUsePushState).usePushState;
|
| + return new Router(useFragment: useFragment,
|
| + windowImpl: injector.get(Window));
|
| + });
|
| + type(NgRoutingHelper);
|
| + value(RouteProvider, null);
|
| + value(RouteInitializer, null);
|
| +
|
| + // directives
|
| + value(NgViewDirective, null);
|
| + type(NgBindRouteDirective);
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Allows configuration of [Router.useFragment]. By default [usePushState] is
|
| + * true, so router will be listen to [Window.onPopState] and route URLs like
|
| + * "http://host:port/foo/bar?baz=qux". Both path and query parts of the URL
|
| + * are used by the router. If [usePushState] is false, router will listen to
|
| + * [Window.onHashChange] and route URLs like
|
| + * "http://host:port/path#/foo/bar?baz=qux". Everything after hash (#) is used
|
| + * by the router.
|
| + */
|
| +@NgInjectableService()
|
| +class NgRoutingUsePushState {
|
| + final bool usePushState;
|
| + NgRoutingUsePushState(): usePushState = true;
|
| + NgRoutingUsePushState.value(this.usePushState);
|
| +}
|
|
|