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

Commit 49c9bde

Browse files
committed
Merge pull request #12 from pavelgj/directive-scoping
Implemented directive scoping
2 parents 8d98d0b + e83854a commit 49c9bde

File tree

6 files changed

+184
-46
lines changed

6 files changed

+184
-46
lines changed

lib/angular.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
library angular;
22

3+
import "dart:collection";
34
import "dart:mirrors";
45
import "dart:async" as async;
56
import "dart:json" as json;
@@ -110,6 +111,15 @@ class AngularModule extends Module {
110111

111112
AngularModule() {
112113
value(DirectiveRegistry, _directives);
114+
type(Compiler, Compiler);
115+
type(BlockFactory, BlockFactory);
116+
type(BlockTypeFactory, BlockTypeFactory);
117+
type(BlockListFactory, BlockListFactory);
118+
type(ExceptionHandler, ExceptionHandler);
119+
type(Scope, Scope);
120+
type(Parser, Parser);
121+
type(Interpolate, Interpolate);
122+
type(Http, Http);
113123
}
114124

115125
directive(Type directive) {

lib/block.dart

Lines changed: 96 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ class BlockCache {
2828

2929
flush([Function callback]) {
3030
groupCache.forEach((blocks) {
31-
while(!blocks.isEmpty) {
31+
while(blocks.isNotEmpty) {
3232
Block block = blocks.removeLast();
33-
if (?callback) callback(block);
33+
if (callback != null) callback(block);
3434
}
3535
});
3636
}
@@ -84,10 +84,10 @@ class Block implements ElementWrapper {
8484
ASSERT(elements != null);
8585
ASSERT(directivePositions != null);
8686
ASSERT(blockCaches != null);
87-
_link(elements, directivePositions, blockCaches);
87+
_link(elements, directivePositions, blockCaches, $injector);
8888
}
8989

90-
_link(List<dom.Node> nodeList, List directivePositions, List<BlockCache> blockCaches) {
90+
_link(List<dom.Node> nodeList, List directivePositions, List<BlockCache> blockCaches, Injector parentInjector) {
9191
var stack;
9292
try {throw '';} catch(e,s) {stack = s;}
9393
var preRenderedIndexOffset = 0;
@@ -112,7 +112,7 @@ class Block implements ElementWrapper {
112112

113113
Map<String, BlockListFactory> anchorsByName = {};
114114
List<String> directiveNames = [];
115-
115+
var injector = parentInjector;
116116
if (directiveRefs != null) {
117117
for (var j = 0, jj = directiveRefs.length; j < jj; j++) {
118118
var blockCache;
@@ -135,10 +135,10 @@ class Block implements ElementWrapper {
135135
anchorsByName[name] = $blockListFactory([node], directiveRef.blockTypes, blockCache);
136136
}
137137
}
138-
_instantiateDirectives(directiveDefsByName, directiveNames, node, anchorsByName);
138+
injector = _instantiateDirectives(directiveDefsByName, directiveNames, node, anchorsByName, parentInjector);
139139
}
140140
if (childDirectivePositions != null) {
141-
_link(node.nodes, childDirectivePositions, blockCaches);
141+
_link(node.nodes, childDirectivePositions, blockCaches, injector);
142142
}
143143

144144
if (fakeParent) {
@@ -148,10 +148,11 @@ class Block implements ElementWrapper {
148148
}
149149
}
150150

151-
_instantiateDirectives(Map<String, DirectiveRef> directiveDefsByName,
151+
Injector _instantiateDirectives(Map<String, DirectiveRef> directiveDefsByName,
152152
List<String> directiveNames,
153153
dom.Node node,
154-
Map<String, BlockList> anchorsByName) {
154+
Map<String, BlockList> anchorsByName,
155+
Injector parentInjector) {
155156
var elementModule = new Module();
156157
elementModule.value(Block, this);
157158
elementModule.value(dom.Element, node);
@@ -160,45 +161,88 @@ class Block implements ElementWrapper {
160161
def.directive.type, def.directive.type));
161162

162163
for (var i = 0, ii = directiveNames.length; i < ii; i++) {
163-
var directiveName = directiveNames[i];
164-
DirectiveRef directiveRef = directiveDefsByName[directiveName];
165-
166-
var directiveModule = new Module();
167-
168-
directiveModule.value(DirectiveValue,
169-
new DirectiveValue.fromString(directiveRef.value));
170-
171-
directiveModule.value(BlockList, anchorsByName[directiveName]);
172-
164+
DirectiveRef directiveRef = directiveDefsByName[directiveNames[i]];
173165
Type directiveType = directiveRef.directive.type;
166+
var visibility = local;
167+
if (directiveRef.directive.$visibility == DirectiveVisibility.CHILDREN) {
168+
visibility = null;
169+
} else if (directiveRef.directive.$visibility == DirectiveVisibility.DIRECT_CHILDREN) {
170+
visibility = directChildren;
171+
}
172+
elementModule.type(directiveType, directiveType, creation: directOnly, visibility: visibility);
173+
}
174174

175-
var injector = $injector.createChild(
176-
[elementModule, directiveModule],
177-
[directiveType]);
178-
179-
try {
180-
var directiveInstance = injector.get(directiveType);
181-
if (directiveRef.directive.isComponent) {
182-
directiveInstance = new ComponentWrapper(directiveRef, directiveInstance, node,
183-
$injector.get(Parser), $injector.get(Compiler), $injector.get(Http));
175+
var injector = parentInjector.createChild([elementModule]);
176+
177+
int prevInstantiatedCount;
178+
List<String> alreadyInstantiated = <String>[];
179+
// TODO(pavelgj): this is a workaround for the lack of directive
180+
// instantiation ordering. A better way is to sort directives in the
181+
// order they must be instantiated in.
182+
do {
183+
prevInstantiatedCount = alreadyInstantiated.length;
184+
for (var i = 0, ii = directiveNames.length; i < ii; i++) {
185+
var directiveName = directiveNames[i];
186+
if (alreadyInstantiated.contains(directiveName)) continue;
187+
DirectiveRef directiveRef = directiveDefsByName[directiveName];
188+
189+
Map<Type, dynamic> locals = new HashMap<Type, dynamic>();
190+
locals[DirectiveValue] =
191+
new DirectiveValue.fromString(directiveRef.value);
192+
locals[BlockList] = anchorsByName[directiveName];
193+
194+
Type directiveType = directiveRef.directive.type;
195+
196+
try {
197+
var directiveInstance = injector.instantiate(directiveType, locals);
198+
alreadyInstantiated.add(directiveName);
199+
if (directiveRef.directive.isComponent) {
200+
directiveInstance = new ComponentWrapper(directiveRef, directiveInstance, node,
201+
$injector.get(Parser), $injector.get(Compiler), $injector.get(Http));
184202

185-
}
186-
directives.add(directiveInstance);
187-
} catch (e,s) {
188-
var msg;
189-
if (e is MirroredUncaughtExceptionError) {
190-
//TODO(misko): why is this here? Injector should never throw this exception
191-
msg = e.exception_string + "\n ORIGINAL Stack trace:\n" + e.stacktrace.toString();
192-
} else {
193-
msg = "Creating $directiveName: " + e.toString() +
203+
}
204+
directives.add(directiveInstance);
205+
} catch (e, s) {
206+
if (e is MirroredUncaughtExceptionError) {
207+
//TODO(misko): why is this here? Injector should never throw this exception
208+
throw e.exception_string + "\n ORIGINAL Stack trace:\n" + e.stacktrace.toString();
209+
} else if (e is IndirectInstantiationError) {
210+
// ignore.
211+
} else {
212+
throw "Creating $directiveName: " + e.toString() +
194213
"\n ORIGINAL Stack trace:\n" + s.toString();
214+
}
195215
}
196-
197-
throw msg;
198216
}
217+
} while(alreadyInstantiated.length != prevInstantiatedCount);
218+
219+
if (alreadyInstantiated.length != directiveNames.length) {
220+
throw 'Cyclic dependency in directives on $node.';
199221
}
222+
return injector;
200223
}
201224

225+
226+
/// DI creation strategy that only allows 'explicit' injection.
227+
dynamic directOnly(Symbol type,
228+
Injector requesting,
229+
Injector defining,
230+
bool directInstantation,
231+
Factory factory) {
232+
if (!directInstantation) {
233+
throw new IndirectInstantiationError(type);
234+
}
235+
return factory();
236+
}
237+
238+
/// DI visibility callback allowin node-local visibility.
239+
bool local(Injector requesting, Injector defining) =>
240+
identical(requesting, defining);
241+
242+
/// DI visibility callback allowin visibility from direct child into parent.
243+
bool directChildren(Injector requesting, Injector defining) =>
244+
local(requesting, defining) || identical(requesting.parent, defining);
245+
202246
attach(Scope scope) {
203247
// Attach directives
204248
for(var i = 0, ii = directives.length; i < ii; i++) {
@@ -336,6 +380,19 @@ class Block implements ElementWrapper {
336380
}
337381
}
338382

383+
class IndirectInstantiationError {
384+
IndirectInstantiationError(type)
385+
: exception_string = '$type must be directly instantated before being '
386+
'injected from child injection';
387+
388+
/** The result of toString() for the exception object. */
389+
final String exception_string;
390+
391+
String toString() {
392+
return exception_string;
393+
}
394+
}
395+
339396
class ComponentWrapper {
340397
DirectiveRef directiveRef;
341398
dynamic controller;

lib/directive.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class Directive {
1515
String $templateUrl;
1616
String $cssUrl;
1717
Map<String, String> $map;
18+
String $visibility;
1819

1920
bool isComponent = false;
2021
bool isStructural = false;
@@ -46,6 +47,10 @@ class Directive {
4647
$templateUrl = reflectStaticField(type, '\$templateUrl');
4748
$cssUrl = reflectStaticField(type, '\$cssUrl');
4849
$priority = reflectStaticField(type, '\$priority');
50+
$visibility = reflectStaticField(type, '\$visibility');
51+
if ($visibility == null) {
52+
$visibility = DirectiveVisibility.LOCAL;
53+
}
4954
$map = reflectStaticField(type, '\$map');
5055
if ($priority == null) {
5156
$priority = 0;
@@ -108,6 +113,12 @@ class DirectiveValue {
108113
DirectiveValue.fromString(this.value);
109114
}
110115

116+
abstract class DirectiveVisibility {
117+
static const String LOCAL = 'local';
118+
static const String CHILDREN = 'children';
119+
static const String DIRECT_CHILDREN = 'direct_children';
120+
}
121+
111122
class Controller {
112123

113124
}

test/_specs.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:angular/angular.dart';
99
import 'jasmine_syntax.dart';
1010
import 'package:di/di.dart';
1111
import 'package:unittest/mock.dart';
12+
import "_log.dart";
1213

1314
export 'package:unittest/unittest.dart';
1415
export 'package:angular/debug.dart';
@@ -149,14 +150,16 @@ class SpecInjector {
149150
List<Module> modules = [new Module()..value(Expando, new Expando('specExpando'))];
150151

151152
module(Function fn) {
152-
Module module = new AngularModule();
153+
Module module = new AngularModule()
154+
..type(Log, Log)
155+
..type(Logger, Logger);
153156
modules.add(module);
154157
fn(module);
155158
}
156159

157160
inject(Function fn, declarationStack) {
158161
if (injector == null) {
159-
injector = new Injector(modules);
162+
injector = new Injector(modules, false); // Implicit injection is disabled.
160163
}
161164
try {
162165
injector.invoke(fn);

test/compiler_spec.dart

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
11
import "_specs.dart";
2+
import "_log.dart";
23
import "dart:mirrors";
34

5+
6+
class TabComponent {
7+
static String $visibility = DirectiveVisibility.DIRECT_CHILDREN;
8+
int id = 0;
9+
Log log;
10+
LocalAttrDirective local;
11+
TabComponent(Log this.log, LocalAttrDirective this.local);
12+
attach(Scope scope) {
13+
log('TabComponent-${id++}');
14+
local.ping();
15+
}
16+
}
17+
18+
class PaneComponent {
19+
TabComponent tabComponent;
20+
LocalAttrDirective localDirective;
21+
Log log;
22+
PaneComponent(TabComponent this.tabComponent, LocalAttrDirective this.localDirective, Log this.log);
23+
attach(Scope scope) {
24+
log('PaneComponent-${tabComponent.id++}');
25+
localDirective.ping();
26+
}
27+
}
28+
29+
class LocalAttrDirective {
30+
static String $visibility = DirectiveVisibility.LOCAL;
31+
int id = 0;
32+
Log log;
33+
LocalAttrDirective(Log this.log);
34+
attach(Scope scope) {}
35+
ping() {
36+
log('LocalAttrDirective-${id++}');
37+
}
38+
}
39+
40+
441
main() {
542

643
describe('dte.compiler', () {
@@ -9,10 +46,12 @@ main() {
946
DirectiveRegistry directives;
1047

1148
beforeEach(inject((Injector injector) {
12-
directives = injector.get(DirectiveRegistry);
13-
14-
directives.register(NgBindAttrDirective);
15-
directives.register(NgRepeatAttrDirective);
49+
directives = injector.get(DirectiveRegistry)
50+
..register(NgBindAttrDirective)
51+
..register(NgRepeatAttrDirective)
52+
..register(TabComponent)
53+
..register(PaneComponent)
54+
..register(LocalAttrDirective);
1655

1756
$rootScope = injector.get(Scope);
1857
}));
@@ -458,6 +497,24 @@ main() {
458497
component.scope.ondone();
459498
expect($rootScope.done).toEqual(true);
460499
}));
500+
501+
});
502+
503+
describe('controller scoping', () {
504+
505+
it('shoud make controllers available to sibling and child controllers', inject((Compiler $compile, Scope $rootScope, Log log) {
506+
var element = $('<tab local><pane local></pane><pane local></pane></tab>');
507+
$compile(element)(element)..attach($rootScope);
508+
expect(log.result()).toEqual('TabComponent-0; LocalAttrDirective-0; PaneComponent-1; LocalAttrDirective-0; PaneComponent-2; LocalAttrDirective-0');
509+
}));
510+
511+
it('should throw an exception if required directive is missing', inject((Compiler $compile, Scope $rootScope) {
512+
expect(() {
513+
var element = $('<tab local><pane></pane><pane local></pane></tab>');
514+
$compile(element)(element)..attach($rootScope);
515+
}, throwsA(startsWith('Creating pane: Illegal argument(s): No provider found for LocalAttrDirective! (resolving LocalAttrDirective)')));
516+
}));
517+
461518
});
462519
});
463520
}

test/selector_spec.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class DirectiveInfosMatcher extends BaseMatcher {
111111
return description;
112112
}
113113

114-
bool matches(directiveRefs, MatchState matchState) {
114+
bool matches(directiveRefs, Map matchState) {
115115
var pass = expected.length == directiveRefs.length;
116116
if (pass) {
117117
for(var i = 0, ii = expected.length; i < ii; i++) {

0 commit comments

Comments
 (0)