Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

feat(routing): new DSL and deferred module loading #525

Merged
merged 4 commits into from
Feb 7, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/core/registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ abstract class AnnotationsMap<K> {
@NgInjectableService()
class MetadataExtractor {
Iterable call(Type type) {
if (reflectType(type) is TypedefMirror) return [];
var metadata = reflectClass(type).metadata;
if (metadata == null) return [];
return metadata.map((InstanceMirror im) => im.reflectee);
Expand Down
6 changes: 3 additions & 3 deletions lib/core/scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ class Scope implements Map {
scopeCount++;
for (_Watch watch = watchers.head; watch != null; watch = watch.next) {
var last = watch.last;
var value = watch.get(scope, _filters);
var value = watch.get(scope, scope._filters);
if (!_identical(value, last)) {
return _digestHandleDirty(scope, watch, last, value, null);
}
Expand All @@ -581,7 +581,7 @@ class Scope implements Map {
for (_Watch watch = watchers.head; watch != null; watch = watch.next) {
if (identical(stopWatch, watch)) return null;
var last = watch.last;
var value = watch.get(scope, _filters);
var value = watch.get(scope, scope._filters);
if (!_identical(value, last)) {
return _digestHandleDirty(scope, watch, last, value, log);
}
Expand Down Expand Up @@ -610,7 +610,7 @@ class Scope implements Map {
watch = scope._watchers.head;
}
last = watch.last;
value = watch.get(scope, _filters);
value = watch.get(scope, scope._filters);
}
}

Expand Down
11 changes: 11 additions & 0 deletions lib/core_dom/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,14 @@ class DirectiveRef {
}
}

/**
* Creates a child injector that allows loading new directives, filters and
* services from the provided modules.
*/
Injector forceNewDirectivesAndFilters(Injector injector, List<Module> modules) {
modules.add(new Module()
..factory(Scope,
(i) => i.parent.get(Scope).$new(filters: i.get(FilterMap))));
return injector.createChild(modules,
forceNewInstances: [DirectiveMap, FilterMap]);
}
1 change: 1 addition & 0 deletions lib/routing/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ class NgRoutingModule extends Module {
type(NgRoutingHelper);
value(RouteProvider, null);
value(RouteInitializer, null);
value(RouteInitializerFn, null);

// directives
value(NgViewDirective, null);
Expand Down
20 changes: 12 additions & 8 deletions lib/routing/ng_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,15 @@ part of angular.routing;
class NgViewDirective implements NgDetachAware, RouteProvider {
final NgRoutingHelper locationService;
final BlockCache blockCache;
final Scope scope;
final Injector injector;
final Element element;
final DirectiveMap directives;
RouteHandle _route;

Block _previousBlock;
Scope _previousScope;
Route _viewRoute;

NgViewDirective(this.element, this.blockCache, this.scope, Injector injector,
Router router, this.directives)
NgViewDirective(this.element, this.blockCache, Injector injector, Router router)
: injector = injector, locationService = injector.get(NgRoutingHelper) {
RouteProvider routeProvider = injector.parent.get(NgViewDirective);
if (routeProvider != null) {
Expand All @@ -98,7 +95,7 @@ class NgViewDirective implements NgDetachAware, RouteProvider {
locationService._unregisterPortal(this);
}

_show(String templateUrl, Route route) {
_show(String templateUrl, Route route, List<Module> modules) {
assert(route.isActive);

if (_viewRoute != null) return;
Expand All @@ -112,11 +109,18 @@ class NgViewDirective implements NgDetachAware, RouteProvider {
_cleanUp();
});

blockCache.fromUrl(templateUrl, directives).then((blockFactory) {
var viewInjector = injector;
if (modules != null) {
viewInjector = forceNewDirectivesAndFilters(viewInjector, modules);
}

var newDirectives = viewInjector.get(DirectiveMap);
blockCache.fromUrl(templateUrl, newDirectives).then((blockFactory) {
_cleanUp();
_previousScope = scope.$new();
_previousScope = viewInjector.get(Scope).$new();
_previousBlock = blockFactory(
injector.createChild([new Module()..value(Scope, _previousScope)]));
viewInjector.createChild(
[new Module()..value(Scope, _previousScope)]));

_previousBlock.elements.forEach((elm) => element.append(elm));
});
Expand Down
117 changes: 106 additions & 11 deletions lib/routing/routing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,76 @@ class ViewFactory {
ViewFactory(this.locationService);

call(String templateUrl) =>
(RouteEvent event) =>
locationService._route(event.route, templateUrl, fromEvent: true);
(RouteEnterEvent event) => _enterHandler(event, templateUrl);

_enterHandler(RouteEnterEvent event, String templateUrl, [List<Module> modules]) =>
locationService._route(event.route, templateUrl, fromEvent: true, modules: modules);

configure(Map<String, NgRouteCfg> config) =>
_configure(locationService.router.root, config);

_configure(Route route, Map<String, NgRouteCfg> config) {
config.forEach((name, cfg) {
var moduledCalled = false;
List<Module> newModules;
route.addRoute(
name: name,
path: cfg.path,
defaultRoute: cfg.defaultRoute,
enter: (RouteEnterEvent e) {
if (cfg.view != null) {
_enterHandler(e, cfg.view, newModules);
}
if (cfg.enter != null) {
cfg.enter(e);
}
},
preEnter: (RoutePreEnterEvent e) {
if (cfg.modules != null && !moduledCalled) {
moduledCalled = true;
var modules = cfg.modules();
if (modules is Future) {
e.allowEnter(modules.then((List<Module> m) {
newModules = m;
return true;
}));
} else {
newModules = modules;
}
}
if (cfg.preEnter != null) {
cfg.preEnter(e);
}
},
leave: cfg.leave,
mount: (Route mountRoute) {
if (cfg.mount != null) {
_configure(mountRoute, cfg.mount);
}
});
});
}
}

NgRouteCfg ngRoute({String path, String view, Map<String, NgRouteCfg> mount,
modules(), bool defaultRoute: false, RoutePreEnterEventHandler preEnter,
RouteEnterEventHandler enter, RouteLeaveEventHandler leave}) =>
new NgRouteCfg(path: path, view: view, mount: mount, modules: modules,
defaultRoute: defaultRoute, preEnter: preEnter, enter: enter,
leave: leave);

class NgRouteCfg {
final String path;
final String view;
final Map<String, NgRouteCfg> mount;
final Function modules;
final bool defaultRoute;
final RouteEnterEventHandler enter;
final RoutePreEnterEventHandler preEnter;
final RouteLeaveEventHandler leave;

NgRouteCfg({this.view, this.path, this.mount, this.modules, this.defaultRoute,
this.enter, this.preEnter, this.leave});
}

/**
Expand All @@ -19,11 +87,23 @@ class ViewFactory {
*
* The [init] method will be called by the framework once the router is
* instantiated but before [NgBindRouteDirective] and [NgViewDirective].
*
* Deprecated: use RouteInitializerFn instead.
*/
@deprecated
abstract class RouteInitializer {
void init(Router router, ViewFactory viewFactory);
}

/**
* An typedef that must be implemented by the user of routing library and
* should include the route initialization.
*
* The function will be called by the framework once the router is
* instantiated but before [NgBindRouteDirective] and [NgViewDirective].
*/
typedef void RouteInitializerFn(Router router, ViewFactory viewFactory);

/**
* A singleton helper service that handles routing initialization, global
* events and view registries.
Expand All @@ -33,15 +113,22 @@ class NgRoutingHelper {
final Router router;
final NgApp _ngApp;
List<NgViewDirective> portals = <NgViewDirective>[];
Map<String, String> _templates = new Map<String, String>();
Map<String, _View> _templates = new Map<String, _View>();

NgRoutingHelper(RouteInitializer initializer, this.router, this._ngApp) {
if (initializer == null) {
NgRoutingHelper(RouteInitializer initializer, Injector injector, this.router, this._ngApp) {
// TODO: move this to constructor parameters when di issue is fixed:
// https://github.com/angular/di.dart/issues/40
RouteInitializerFn initializerFn = injector.get(RouteInitializerFn);
if (initializer == null && initializerFn == null) {
window.console.error('No RouteInitializer implementation provided.');
return;
};

initializer.init(router, new ViewFactory(this));
if (initializerFn != null) {
initializerFn(router, new ViewFactory(this));
} else {
initializer.init(router, new ViewFactory(this));
}
router.onRouteStart.listen((RouteStartEvent routeEvent) {
routeEvent.completed.then((success) {
if (success) {
Expand All @@ -60,23 +147,24 @@ class NgRoutingHelper {
activePath = activePath.skip(_routeDepth(startingFrom));
}
for (Route route in activePath) {
var templateUrl = _templates[_routePath(route)];
if (templateUrl == null) continue;
var viewDef = _templates[_routePath(route)];
if (viewDef == null) continue;
var templateUrl = viewDef.template;

NgViewDirective view = portals.lastWhere((NgViewDirective v) {
return _routePath(route) != _routePath(v._route) &&
_routePath(route).startsWith(_routePath(v._route));
}, orElse: () => null);
if (view != null && !alreadyActiveViews.contains(view)) {
view._show(templateUrl, route);
view._show(templateUrl, route, viewDef.modules);
alreadyActiveViews.add(view);
break;
}
}
}

_route(Route route, String template, {bool fromEvent}) {
_templates[_routePath(route)] = template;
_route(Route route, String template, {bool fromEvent, List<Module> modules}) {
_templates[_routePath(route)] = new _View(template, modules);
}

_registerPortal(NgViewDirective ngView) {
Expand All @@ -88,6 +176,13 @@ class NgRoutingHelper {
}
}

class _View {
final String template;
final List<Module> modules;

_View(this.template, this.modules);
}

String _routePath(Route route) {
var path = [];
var p = route;
Expand Down
2 changes: 1 addition & 1 deletion pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ packages:
route_hierarchical:
description: route_hierarchical
source: hosted
version: "0.4.11"
version: "0.4.12"
shadow_dom:
description: shadow_dom
source: hosted
Expand Down
12 changes: 12 additions & 0 deletions test/core/registry_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,20 @@ main() => describe('RegistryMap', () {
expect(keys).toEqual([new MyAnnotation('A'), new MyAnnotation('B')]);
expect(types).toEqual([A1, A1]);
});

it('should safely ignore typedefs', () {
var module = new Module()
..type(MyMap)
..type(MetadataExtractor)
..value(MyTypedef, (String _) => null);

var injector = new DynamicInjector(modules: [module]);
expect(() => injector.get(MyMap), isNot(throws));
});
});

typedef void MyTypedef(String arg);

class MyMap extends AnnotationMap<MyAnnotation> {
MyMap(Injector injector, MetadataExtractor metadataExtractor)
: super(injector, metadataExtractor);
Expand Down
38 changes: 38 additions & 0 deletions test/core/scope_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,30 @@ main() {
expect(scopeLocal['c']).toEqual('cW');
});
});

describe('filters', () {

it('should use filters from correct scope when digesting scope trees', inject((Scope rootScope, Injector injector) {
var withFilterOne = injector.createChild([new Module()..type(FilterOne)],
forceNewInstances: [FilterMap]).get(FilterMap);
var withFilterTwo = injector.createChild([new Module()..type(FilterTwo)],
forceNewInstances: [FilterMap]).get(FilterMap);

var childScopeOne = rootScope.$new(filters: withFilterOne);
var childScopeTwo = rootScope.$new(filters: withFilterTwo);

var valueOne;
var valueTwo;
childScopeOne.$watch('"str" | newFilter', (val) => valueOne = val);
childScopeTwo.$watch('"str" | newFilter', (val) => valueTwo = val);

rootScope.$digest();

expect(valueOne).toEqual('str 1');
expect(valueTwo).toEqual('str 2');
}));

});
});
}

Expand Down Expand Up @@ -1337,3 +1361,17 @@ class _JsonableIterableWrapper<T> implements Iterable<T> {

toJson() => source.toList();
}

@NgFilter(name:'newFilter')
class FilterOne {
call(String str) {
return '$str 1';
}
}

@NgFilter(name:'newFilter')
class FilterTwo {
call(String str) {
return '$str 2';
}
}
Loading