From 6ef8e828f43b0ef25f095bea2b063ad4a2eeb4e0 Mon Sep 17 00:00:00 2001 From: Pavel J Date: Mon, 11 Nov 2013 10:08:04 -0800 Subject: [PATCH] refactor(routing): simplified routing API Removed ng-bind-route directive, RouteProvider works with ng-view to find current route and parameters. Closes #255 --- lib/routing/module.dart | 12 +-- lib/routing/ng_bind_route.dart | 105 --------------------------- lib/routing/ng_view.dart | 102 +++++++++++++++++++++----- lib/routing/routing.dart | 4 +- test/routing/ng_bind_route_spec.dart | 81 --------------------- test/routing/ng_view_spec.dart | 6 +- 6 files changed, 94 insertions(+), 216 deletions(-) delete mode 100644 lib/routing/ng_bind_route.dart delete mode 100644 test/routing/ng_bind_route_spec.dart diff --git a/lib/routing/module.dart b/lib/routing/module.dart index d2f1834c0..ca5faf377 100644 --- a/lib/routing/module.dart +++ b/lib/routing/module.dart @@ -70,18 +70,16 @@ * order to know which recipe to load. Lets consider the following * `viewRecipe.html`. * - * + * * * The template contains a custom `view-recipe` component that handles the - * displaying of the recipe. We also use `ng-bind-route` directive to bind - * that compomnent to "viewRecipe" route. Now, our `view-recipe` can inject - * [RouteProvider] to get hold of the route and its parameters. It might look - * like this: + * 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.route.parameters['recipeId']; + * String recipeId = routeProvider.parameters['recipeId']; * _loadRecipe(recipeId); * } * } @@ -164,7 +162,6 @@ 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}) { @@ -180,7 +177,6 @@ class NgRoutingModule extends Module { // directives type(NgViewDirective); - type(NgBindRouteDirective); } } diff --git a/lib/routing/ng_bind_route.dart b/lib/routing/ng_bind_route.dart deleted file mode 100644 index 83d4115ab..000000000 --- a/lib/routing/ng_bind_route.dart +++ /dev/null @@ -1,105 +0,0 @@ -part of angular.routing; - -/** - * A directive that allows to bind child components/directives to a specific - * route. - * - *
- * - *
- * - * ng-bind-route directives can be nested. - * - *
- *
- * - *
- *
- * - * The '.' prefix indicates that bar route is relative to the route in the - * parent ng-bind-route directive. - */ -@NgDirective( - visibility: NgDirective.CHILDREN_VISIBILITY, - publishTypes: const [RouteProvider], - selector: '[ng-bind-route]', - map: const { - 'ng-bind-route': '@routeName' - } -) -class NgBindRouteDirective implements RouteProvider { - Router _router; - String routeName; - Injector _injector; - - NgBindRouteDirective(Router this._router, Injector this._injector); - - /// Returns the parent [RouteProvider]. - RouteProvider get _parent => _injector.parent.get(RouteProvider); - - Route get route => _router.root.getRoute(routePath); - - String get routePath { - if (!routeName.startsWith('.')) { - return routeName; - } - String parentPath; - if (_parent == null) { - parentPath = ''; - } else { - parentPath = _parent.routePath + '.'; - } - return parentPath + routeName.substring(1); - } -} - -/** - * Class that can be injected to retrieve information about the current route. - * For example: - * - * @NgComponent(/* ... */) - * class MyComponent implement NgDetachAware { - * RouteHandle route; - * - * MyComponent(RouteProvider routeProvider) { - * route = routeProvider.route; - * route.onRoute.listen((RouteEvent e) { - * // Do something when the route is activated. - * }); - * route.onLeave.listen((RouteEvent e) { - * // Do something when the route is diactivated. - * e.allowLeave(allDataSaved()); - * }); - * } - * - * detach() { - * // The route handle must be discarded. - * route.discard(); - * } - * - * Future allDataSaved() { - * // Check that all data is saved and confirm with the user if needed. - * } - * } - * - * If user component is used outside of ng-bind-route directive then - * injected [RouteProvider] will be null. - */ -abstract class RouteProvider { - - /** - * Returns [Route] for [routePath]. - */ - Route get route; - - /** - * Returns the name of the current route. - */ - String get routeName; - - /** - * Returns full path of the current route. - */ - String get routePath; -} - diff --git a/lib/routing/ng_view.dart b/lib/routing/ng_view.dart index 461e5b900..41d38f569 100644 --- a/lib/routing/ng_view.dart +++ b/lib/routing/ng_view.dart @@ -43,7 +43,7 @@ part of angular.routing; * * library.html: * - *
+ *
*

Library!

* * @@ -56,54 +56,58 @@ part of angular.routing; *
  • Book 23456 * */ -@NgDirective(selector: 'ng-view') -class NgViewDirective implements NgDetachAware { +@NgDirective( + selector: 'ng-view', + publishTypes: const [RouteProvider], + visibility: NgDirective.CHILDREN_VISIBILITY +) +class NgViewDirective implements NgDetachAware, RouteProvider { final _RoutingHelper locationService; final BlockCache blockCache; final Scope scope; final Injector injector; final Element element; - RouteHandle route; - bool _showingRoute = false; + RouteHandle _route; Block _previousBlock; Scope _previousScope; + Route _viewRoute; - NgViewDirective(Element this.element, RouteProvider routeProvider, - BlockCache this.blockCache, Scope this.scope, - Injector injector, Router router): - injector = injector, locationService = injector.get(_RoutingHelper) { + NgViewDirective(Element this.element, BlockCache this.blockCache, + Scope this.scope, Injector injector, Router router) + : injector = injector, locationService = injector.get(_RoutingHelper) { + RouteProvider routeProvider = injector.parent.get(RouteProvider); if (routeProvider != null) { - route = routeProvider.route.newHandle(); + _route = routeProvider.route.newHandle(); } else { - route = router.root.newHandle(); + _route = router.root.newHandle(); } locationService._registerPortal(this); _maybeReloadViews(); } void _maybeReloadViews() { - if (route.isActive) { - locationService._reloadViews(startingFrom: route); + if (_route.isActive) { + locationService._reloadViews(startingFrom: _route); } } detach() { - route.discard(); + _route.discard(); locationService._unregisterPortal(this); } _show(String templateUrl, Route route) { assert(route.isActive); - if (_showingRoute) return; - _showingRoute = true; + if (_viewRoute != null) return; + _viewRoute = route; StreamSubscription _leaveSubscription; _leaveSubscription = route.onLeave.listen((_) { _leaveSubscription.cancel(); _leaveSubscription = null; - _showingRoute = false; + _viewRoute = null; _cleanUp(); }); @@ -128,4 +132,68 @@ class NgViewDirective implements NgDetachAware { _previousBlock = null; _previousScope = null; } + + Route get route => _viewRoute; + String get routeName => _viewRoute.name; + Map get parameters { + var res = {}; + var p = _viewRoute; + while (p != null) { + res.addAll(p.parameters); + p = p.parent; + } + return res; + } +} + + +/** + * Class that can be injected to retrieve information about the current route. + * For example: + * + * @NgComponent(/* ... */) + * class MyComponent implement NgDetachAware { + * RouteHandle route; + * + * MyComponent(RouteProvider routeProvider) { + * _loadFoo(routeProvider.parameters['fooId']); + * route = routeProvider.route.newHandle(); + * route.onRoute.listen((RouteEvent e) { + * // Do something when the route is activated. + * }); + * route.onLeave.listen((RouteEvent e) { + * // Do something when the route is diactivated. + * e.allowLeave(allDataSaved()); + * }); + * } + * + * detach() { + * // The route handle must be discarded. + * route.discard(); + * } + * + * Future allDataSaved() { + * // Check that all data is saved and confirm with the user if needed. + * } + * } + * + * If user component is used outside of ng-view directive then + * injected [RouteProvider] will be null. + */ +abstract class RouteProvider { + + /** + * Returns [Route] for current view. + */ + Route get route; + + /** + * Returns the name of the current route. + */ + String get routeName; + + /** + * Returns parameters for this route. + */ + Map get parameters; } diff --git a/lib/routing/routing.dart b/lib/routing/routing.dart index 41246661c..b0ad68b78 100644 --- a/lib/routing/routing.dart +++ b/lib/routing/routing.dart @@ -62,8 +62,8 @@ class _RoutingHelper { if (templateUrl == null) continue; NgViewDirective view = portals.lastWhere((NgViewDirective v) { - return _routePath(route) != _routePath(v.route) && - _routePath(route).startsWith(_routePath(v.route)); + return _routePath(route) != _routePath(v._route) && + _routePath(route).startsWith(_routePath(v._route)); }, orElse: () => null); if (view != null && !alreadyActiveViews.contains(view)) { view._show(templateUrl, route); diff --git a/test/routing/ng_bind_route_spec.dart b/test/routing/ng_bind_route_spec.dart deleted file mode 100644 index a046a7020..000000000 --- a/test/routing/ng_bind_route_spec.dart +++ /dev/null @@ -1,81 +0,0 @@ -library ng_bind_route_spec; - -import 'dart:html'; -import '../_specs.dart'; -import 'package:angular/routing/module.dart'; -import 'package:angular/mock/module.dart'; - -main() { - describe('ngBindRoute', () { - TestBed _; - - beforeEach(module((Module m) { - m - ..install(new AngularMockModule()) - ..type(RouteInitializer, implementedBy: NestedRouteInitializer); - })); - - beforeEach(inject((TestBed tb) { - _ = tb; - })); - - - it('should inject null RouteProvider when no ng-bind-route', async(() { - Element root = _.compile('
    '); - expect(_.rootScope['routeProbe'].injector.get(RouteProvider)).toBeNull(); - })); - - - it('should inject RouteProvider with correct flat route', async(() { - Element root = _.compile( - '
    '); - expect(_.rootScope['routeProbe'].injector.get(RouteProvider).routeName) - .toEqual('library'); - })); - - - it('should inject RouteProvider with correct nested route', async(() { - Element root = _.compile( - '
    ' - '
    ' - '
    ' - '
    ' - '
    '); - expect(_.rootScope['routeProbe'].injector.get(RouteProvider).routePath) - .toEqual('library.all'); - })); - - }); -} - -class NestedRouteInitializer implements RouteInitializer { - void init(Router router, ViewFactory view) { - router.root - ..addRoute( - name: 'library', - path: '/library', - enter: view('library.html'), - mount: (Route route) => route - ..addRoute( - name: 'all', - path: '/all', - enter: view('book_list.html')) - ..addRoute( - name: 'book', - path: '/:bookId', - mount: (Route route) => route - ..addRoute( - name: 'overview', - path: '/overview', - defaultRoute: true, - enter: view('book_overview.html')) - ..addRoute( - name: 'read', - path: '/read', - enter: view('book_read.html')))) - ..addRoute( - name: 'admin', - path: '/admin', - enter: view('admin.html')); - } -} diff --git a/test/routing/ng_view_spec.dart b/test/routing/ng_view_spec.dart index 170ec2098..22c2304b6 100644 --- a/test/routing/ng_view_spec.dart +++ b/test/routing/ng_view_spec.dart @@ -91,14 +91,14 @@ main() { router = _router; templates.put('library.html', new HttpResponse(200, - '

    Library

    ' + '

    Library

    ' '
    ')); templates.put('book_list.html', new HttpResponse(200, '

    Books

    ')); templates.put('book_overview.html', new HttpResponse(200, - '

    Book 1234

    ')); + '

    Book 1234

    ')); templates.put('book_read.html', new HttpResponse(200, - '

    Read Book 1234

    ')); + '

    Read Book 1234

    ')); }));