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

Implemented directive scoping #12

Merged
merged 2 commits into from
Jun 22, 2013
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
10 changes: 10 additions & 0 deletions lib/angular.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
library angular;

import "dart:collection";
import "dart:mirrors";
import "dart:async" as async;
import "dart:json" as json;
Expand Down Expand Up @@ -107,6 +108,15 @@ class AngularModule extends Module {

AngularModule() {
value(DirectiveRegistry, _directives);
type(Compiler, Compiler);
type(BlockFactory, BlockFactory);
type(BlockTypeFactory, BlockTypeFactory);
type(BlockListFactory, BlockListFactory);
type(ExceptionHandler, ExceptionHandler);
type(Scope, Scope);
type(Parser, Parser);
type(Interpolate, Interpolate);
type(Http, Http);
}

directive(Type directive) {
Expand Down
135 changes: 96 additions & 39 deletions lib/block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ class BlockCache {

flush([Function callback]) {
groupCache.forEach((blocks) {
while(!blocks.isEmpty) {
while(blocks.isNotEmpty) {
Block block = blocks.removeLast();
if (?callback) callback(block);
if (callback != null) callback(block);
}
});
}
Expand Down Expand Up @@ -84,10 +84,10 @@ class Block implements ElementWrapper {
ASSERT(elements != null);
ASSERT(directivePositions != null);
ASSERT(blockCaches != null);
_link(elements, directivePositions, blockCaches);
_link(elements, directivePositions, blockCaches, $injector);
}

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

Map<String, BlockListFactory> anchorsByName = {};
List<String> directiveNames = [];

var injector = parentInjector;
if (directiveRefs != null) {
for (var j = 0, jj = directiveRefs.length; j < jj; j++) {
var blockCache;
Expand All @@ -135,10 +135,10 @@ class Block implements ElementWrapper {
anchorsByName[name] = $blockListFactory([node], directiveRef.blockTypes, blockCache);
}
}
_instantiateDirectives(directiveDefsByName, directiveNames, node, anchorsByName);
injector = _instantiateDirectives(directiveDefsByName, directiveNames, node, anchorsByName, parentInjector);
}
if (childDirectivePositions != null) {
_link(node.nodes, childDirectivePositions, blockCaches);
_link(node.nodes, childDirectivePositions, blockCaches, injector);
}

if (fakeParent) {
Expand All @@ -148,10 +148,11 @@ class Block implements ElementWrapper {
}
}

_instantiateDirectives(Map<String, DirectiveRef> directiveDefsByName,
Injector _instantiateDirectives(Map<String, DirectiveRef> directiveDefsByName,
List<String> directiveNames,
dom.Node node,
Map<String, BlockList> anchorsByName) {
Map<String, BlockList> anchorsByName,
Injector parentInjector) {
var elementModule = new Module();
elementModule.value(Block, this);
elementModule.value(dom.Element, node);
Expand All @@ -160,45 +161,88 @@ class Block implements ElementWrapper {
def.directive.type, def.directive.type));

for (var i = 0, ii = directiveNames.length; i < ii; i++) {
var directiveName = directiveNames[i];
DirectiveRef directiveRef = directiveDefsByName[directiveName];

var directiveModule = new Module();

directiveModule.value(DirectiveValue,
new DirectiveValue.fromString(directiveRef.value));

directiveModule.value(BlockList, anchorsByName[directiveName]);

DirectiveRef directiveRef = directiveDefsByName[directiveNames[i]];
Type directiveType = directiveRef.directive.type;
var visibility = local;
if (directiveRef.directive.$visibility == DirectiveVisibility.CHILDREN) {
visibility = null;
} else if (directiveRef.directive.$visibility == DirectiveVisibility.DIRECT_CHILDREN) {
visibility = directChildren;
}
elementModule.type(directiveType, directiveType, creation: directOnly, visibility: visibility);
}

var injector = $injector.createChild(
[elementModule, directiveModule],
[directiveType]);

try {
var directiveInstance = injector.get(directiveType);
if (directiveRef.directive.isComponent) {
directiveInstance = new ComponentWrapper(directiveRef, directiveInstance, node,
$injector.get(Parser), $injector.get(Compiler), $injector.get(Http));
var injector = parentInjector.createChild([elementModule]);

int prevInstantiatedCount;
List<String> alreadyInstantiated = <String>[];
// TODO(pavelgj): this is a workaround for the lack of directive
// instantiation ordering. A better way is to sort directives in the
// order they must be instantiated in.
do {
prevInstantiatedCount = alreadyInstantiated.length;
for (var i = 0, ii = directiveNames.length; i < ii; i++) {
var directiveName = directiveNames[i];
if (alreadyInstantiated.contains(directiveName)) continue;
DirectiveRef directiveRef = directiveDefsByName[directiveName];

Map<Type, dynamic> locals = new HashMap<Type, dynamic>();
locals[DirectiveValue] =
new DirectiveValue.fromString(directiveRef.value);
locals[BlockList] = anchorsByName[directiveName];

Type directiveType = directiveRef.directive.type;

try {
var directiveInstance = injector.instantiate(directiveType, locals);
alreadyInstantiated.add(directiveName);
if (directiveRef.directive.isComponent) {
directiveInstance = new ComponentWrapper(directiveRef, directiveInstance, node,
$injector.get(Parser), $injector.get(Compiler), $injector.get(Http));

}
directives.add(directiveInstance);
} catch (e,s) {
var msg;
if (e is MirroredUncaughtExceptionError) {
//TODO(misko): why is this here? Injector should never throw this exception
msg = e.exception_string + "\n ORIGINAL Stack trace:\n" + e.stacktrace.toString();
} else {
msg = "Creating $directiveName: " + e.toString() +
}
directives.add(directiveInstance);
} catch (e, s) {
if (e is MirroredUncaughtExceptionError) {
//TODO(misko): why is this here? Injector should never throw this exception
throw e.exception_string + "\n ORIGINAL Stack trace:\n" + e.stacktrace.toString();
} else if (e is IndirectInstantiationError) {
// ignore.
} else {
throw "Creating $directiveName: " + e.toString() +
"\n ORIGINAL Stack trace:\n" + s.toString();
}
}

throw msg;
}
} while(alreadyInstantiated.length != prevInstantiatedCount);

if (alreadyInstantiated.length != directiveNames.length) {
throw 'Cyclic dependency in directives on $node.';
}
return injector;
}


/// DI creation strategy that only allows 'explicit' injection.
dynamic directOnly(Symbol type,
Injector requesting,
Injector defining,
bool directInstantation,
Factory factory) {
if (!directInstantation) {
throw new IndirectInstantiationError(type);
}
return factory();
}

/// DI visibility callback allowin node-local visibility.
bool local(Injector requesting, Injector defining) =>
identical(requesting, defining);

/// DI visibility callback allowin visibility from direct child into parent.
bool directChildren(Injector requesting, Injector defining) =>
local(requesting, defining) || identical(requesting.parent, defining);

attach(Scope scope) {
// Attach directives
for(var i = 0, ii = directives.length; i < ii; i++) {
Expand Down Expand Up @@ -336,6 +380,19 @@ class Block implements ElementWrapper {
}
}

class IndirectInstantiationError {
IndirectInstantiationError(type)
: exception_string = '$type must be directly instantated before being '
'injected from child injection';

/** The result of toString() for the exception object. */
final String exception_string;

String toString() {
return exception_string;
}
}

class ComponentWrapper {
DirectiveRef directiveRef;
dynamic controller;
Expand Down
11 changes: 11 additions & 0 deletions lib/directive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Directive {
String $template;
String $templateUrl;
Map<String, String> $map;
String $visibility;

bool isComponent = false;
bool isStructural = false;
Expand Down Expand Up @@ -44,6 +45,10 @@ class Directive {
$template = reflectStaticField(type, '\$template');
$templateUrl = reflectStaticField(type, '\$templateUrl');
$priority = reflectStaticField(type, '\$priority');
$visibility = reflectStaticField(type, '\$visibility');
if ($visibility == null) {
$visibility = DirectiveVisibility.LOCAL;
}
$map = reflectStaticField(type, '\$map');
if ($priority == null) {
$priority = 0;
Expand Down Expand Up @@ -106,6 +111,12 @@ class DirectiveValue {
DirectiveValue.fromString(this.value);
}

abstract class DirectiveVisibility {
static const String LOCAL = 'local';
static const String CHILDREN = 'children';
static const String DIRECT_CHILDREN = 'direct_children';
}

class Controller {

}
7 changes: 5 additions & 2 deletions test/_specs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:angular/angular.dart';
import 'jasmine_syntax.dart';
import 'package:di/di.dart';
import 'package:unittest/mock.dart';
import "_log.dart";

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

module(Function fn) {
Module module = new AngularModule();
Module module = new AngularModule()
..type(Log, Log)
..type(Logger, Logger);
modules.add(module);
fn(module);
}

inject(Function fn, declarationStack) {
if (injector == null) {
injector = new Injector(modules);
injector = new Injector(modules, false); // Implicit injection is disabled.
}
try {
injector.invoke(fn);
Expand Down
65 changes: 61 additions & 4 deletions test/compiler_spec.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,43 @@
import "_specs.dart";
import "_log.dart";
import "dart:mirrors";


class TabComponent {
static String $visibility = DirectiveVisibility.DIRECT_CHILDREN;
int id = 0;
Log log;
LocalAttrDirective local;
TabComponent(Log this.log, LocalAttrDirective this.local);
attach(Scope scope) {
log('TabComponent-${id++}');
local.ping();
}
}

class PaneComponent {
TabComponent tabComponent;
LocalAttrDirective localDirective;
Log log;
PaneComponent(TabComponent this.tabComponent, LocalAttrDirective this.localDirective, Log this.log);
attach(Scope scope) {
log('PaneComponent-${tabComponent.id++}');
localDirective.ping();
}
}

class LocalAttrDirective {
static String $visibility = DirectiveVisibility.LOCAL;
int id = 0;
Log log;
LocalAttrDirective(Log this.log);
attach(Scope scope) {}
ping() {
log('LocalAttrDirective-${id++}');
}
}


main() {

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

beforeEach(inject((Injector injector) {
directives = injector.get(DirectiveRegistry);

directives.register(NgBindAttrDirective);
directives.register(NgRepeatAttrDirective);
directives = injector.get(DirectiveRegistry)
..register(NgBindAttrDirective)
..register(NgRepeatAttrDirective)
..register(TabComponent)
..register(PaneComponent)
..register(LocalAttrDirective);

$rootScope = injector.get(Scope);
}));
Expand Down Expand Up @@ -454,6 +493,24 @@ main() {
component.scope.ondone();
expect($rootScope.done).toEqual(true);
}));

});

describe('controller scoping', () {

it('shoud make controllers available to sibling and child controllers', inject((Compiler $compile, Scope $rootScope, Log log) {
var element = $('<tab local><pane local></pane><pane local></pane></tab>');
$compile(element)(element)..attach($rootScope);
expect(log.result()).toEqual('TabComponent-0; LocalAttrDirective-0; PaneComponent-1; LocalAttrDirective-0; PaneComponent-2; LocalAttrDirective-0');
}));

it('should throw an exception if required directive is missing', inject((Compiler $compile, Scope $rootScope) {
expect(() {
var element = $('<tab local><pane></pane><pane local></pane></tab>');
$compile(element)(element)..attach($rootScope);
}, throwsA(startsWith('Creating pane: Illegal argument(s): No provider found for LocalAttrDirective! (resolving LocalAttrDirective)')));
}));

});
});
}
Expand Down
2 changes: 1 addition & 1 deletion test/selector_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class DirectiveInfosMatcher extends BaseMatcher {
return description;
}

bool matches(directiveRefs, MatchState matchState) {
bool matches(directiveRefs, Map matchState) {
var pass = expected.length == directiveRefs.length;
if (pass) {
for(var i = 0, ii = expected.length; i < ii; i++) {
Expand Down