feat(di): add support for multi bindings
BREAKING CHANGE Previously a content binding of a component was visible to the directives in its view with the host constraint. This is not the case any more. To access that binding, remove the constraint.
This commit is contained in:
parent
2fea0c2602
commit
7736964a37
|
@ -30,6 +30,7 @@ import {
|
|||
InjectorDynamicStrategy,
|
||||
BindingWithVisibility
|
||||
} from 'angular2/src/core/di/injector';
|
||||
import {resolveBinding, ResolvedFactory} from 'angular2/src/core/di/binding';
|
||||
|
||||
import {AttributeMetadata, QueryMetadata} from '../metadata/di';
|
||||
|
||||
|
@ -195,40 +196,32 @@ export class DirectiveDependency extends Dependency {
|
|||
}
|
||||
|
||||
export class DirectiveBinding extends ResolvedBinding {
|
||||
constructor(key: Key, factory: Function, dependencies: Dependency[],
|
||||
public resolvedBindings: ResolvedBinding[],
|
||||
public resolvedViewBindings: ResolvedBinding[],
|
||||
public metadata: RenderDirectiveMetadata) {
|
||||
super(key, factory, dependencies);
|
||||
constructor(key: Key, factory: Function, deps: Dependency[],
|
||||
public metadata: RenderDirectiveMetadata,
|
||||
public bindings: Array<Type | Binding | any[]>,
|
||||
public viewBindings: Array<Type | Binding | any[]>) {
|
||||
super(key, [new ResolvedFactory(factory, deps)], false);
|
||||
}
|
||||
|
||||
get callOnDestroy(): boolean { return this.metadata.callOnDestroy; }
|
||||
|
||||
get callOnChanges(): boolean { return this.metadata.callOnChanges; }
|
||||
|
||||
get callAfterContentChecked(): boolean { return this.metadata.callAfterContentChecked; }
|
||||
|
||||
get displayName(): string { return this.key.displayName; }
|
||||
|
||||
get callOnDestroy(): boolean { return this.metadata.callOnDestroy; }
|
||||
|
||||
get eventEmitters(): string[] {
|
||||
return isPresent(this.metadata) && isPresent(this.metadata.events) ? this.metadata.events : [];
|
||||
}
|
||||
|
||||
get changeDetection() { return this.metadata.changeDetection; }
|
||||
|
||||
static createFromBinding(binding: Binding, meta: DirectiveMetadata): DirectiveBinding {
|
||||
if (isBlank(meta)) {
|
||||
meta = new DirectiveMetadata();
|
||||
}
|
||||
|
||||
var rb = binding.resolve();
|
||||
var deps = ListWrapper.map(rb.dependencies, DirectiveDependency.createFrom);
|
||||
var resolvedBindings = isPresent(meta.bindings) ? Injector.resolve(meta.bindings) : [];
|
||||
var resolvedViewBindings = meta instanceof ComponentMetadata && isPresent(meta.viewBindings) ?
|
||||
Injector.resolve(meta.viewBindings) :
|
||||
[];
|
||||
var rb = resolveBinding(binding);
|
||||
var rf = rb.resolvedFactories[0];
|
||||
var deps = rf.dependencies.map(DirectiveDependency.createFrom);
|
||||
var token = binding.token;
|
||||
var metadata = RenderDirectiveMetadata.create({
|
||||
id: stringify(rb.key.token),
|
||||
id: stringify(binding.token),
|
||||
type: meta instanceof ComponentMetadata ? RenderDirectiveMetadata.COMPONENT_TYPE :
|
||||
RenderDirectiveMetadata.DIRECTIVE_TYPE,
|
||||
selector: meta.selector,
|
||||
|
@ -236,29 +229,30 @@ export class DirectiveBinding extends ResolvedBinding {
|
|||
events: meta.events,
|
||||
host: isPresent(meta.host) ? MapWrapper.createFromStringMap(meta.host) : null,
|
||||
properties: meta.properties,
|
||||
readAttributes: DirectiveBinding._readAttributes(deps),
|
||||
readAttributes: DirectiveBinding._readAttributes(<any>deps),
|
||||
|
||||
callOnDestroy: hasLifecycleHook(LifecycleEvent.OnDestroy, rb.key.token, meta),
|
||||
callOnChanges: hasLifecycleHook(LifecycleEvent.OnChanges, rb.key.token, meta),
|
||||
callDoCheck: hasLifecycleHook(LifecycleEvent.DoCheck, rb.key.token, meta),
|
||||
callOnInit: hasLifecycleHook(LifecycleEvent.OnInit, rb.key.token, meta),
|
||||
callAfterContentInit: hasLifecycleHook(LifecycleEvent.AfterContentInit, rb.key.token, meta),
|
||||
callAfterContentChecked:
|
||||
hasLifecycleHook(LifecycleEvent.AfterContentChecked, rb.key.token, meta),
|
||||
callAfterViewInit: hasLifecycleHook(LifecycleEvent.AfterViewInit, rb.key.token, meta),
|
||||
callAfterViewChecked: hasLifecycleHook(LifecycleEvent.AfterViewChecked, rb.key.token, meta),
|
||||
callOnDestroy: hasLifecycleHook(LifecycleEvent.OnDestroy, token, meta),
|
||||
callOnChanges: hasLifecycleHook(LifecycleEvent.OnChanges, token, meta),
|
||||
callDoCheck: hasLifecycleHook(LifecycleEvent.DoCheck, token, meta),
|
||||
callOnInit: hasLifecycleHook(LifecycleEvent.OnInit, token, meta),
|
||||
callAfterContentInit: hasLifecycleHook(LifecycleEvent.AfterContentInit, token, meta),
|
||||
callAfterContentChecked: hasLifecycleHook(LifecycleEvent.AfterContentChecked, token, meta),
|
||||
callAfterViewInit: hasLifecycleHook(LifecycleEvent.AfterViewInit, token, meta),
|
||||
callAfterViewChecked: hasLifecycleHook(LifecycleEvent.AfterViewChecked, token, meta),
|
||||
|
||||
changeDetection: meta instanceof ComponentMetadata ? meta.changeDetection : null,
|
||||
|
||||
exportAs: meta.exportAs
|
||||
});
|
||||
return new DirectiveBinding(rb.key, rb.factory, deps, resolvedBindings, resolvedViewBindings,
|
||||
metadata);
|
||||
var bindings = isPresent(meta.bindings) ? meta.bindings : [];
|
||||
var viewBindigs =
|
||||
meta instanceof ComponentMetadata && isPresent(meta.viewBindings) ? meta.viewBindings : [];
|
||||
return new DirectiveBinding(rb.key, rf.factory, deps, metadata, bindings, viewBindigs);
|
||||
}
|
||||
|
||||
static _readAttributes(deps) {
|
||||
static _readAttributes(deps: DirectiveDependency[]): string[] {
|
||||
var readAttributes = [];
|
||||
ListWrapper.forEach(deps, (dep) => {
|
||||
deps.forEach(dep => {
|
||||
if (isPresent(dep.attributeName)) {
|
||||
readAttributes.push(dep.attributeName);
|
||||
}
|
||||
|
@ -316,7 +310,7 @@ export class ProtoElementInjector {
|
|||
eventEmitterAccessors: EventEmitterAccessor[][];
|
||||
protoInjector: ProtoInjector;
|
||||
|
||||
static create(parent: ProtoElementInjector, index: number, bindings: ResolvedBinding[],
|
||||
static create(parent: ProtoElementInjector, index: number, bindings: DirectiveBinding[],
|
||||
firstBindingIsComponent: boolean, distanceToParent: number,
|
||||
directiveVariableBindings: Map<string, number>): ProtoElementInjector {
|
||||
var bd = [];
|
||||
|
@ -326,43 +320,46 @@ export class ProtoElementInjector {
|
|||
if (firstBindingIsComponent) {
|
||||
ProtoElementInjector._createViewBindingsWithVisibility(bindings, bd);
|
||||
}
|
||||
ProtoElementInjector._createBindingsWithVisibility(bindings, bd, firstBindingIsComponent);
|
||||
|
||||
ProtoElementInjector._createBindingsWithVisibility(bindings, bd);
|
||||
return new ProtoElementInjector(parent, index, bd, distanceToParent, firstBindingIsComponent,
|
||||
directiveVariableBindings);
|
||||
}
|
||||
|
||||
private static _createDirectiveBindingWithVisibility(dirBindings: ResolvedBinding[],
|
||||
private static _createDirectiveBindingWithVisibility(dirBindings: DirectiveBinding[],
|
||||
bd: BindingWithVisibility[],
|
||||
firstBindingIsComponent: boolean) {
|
||||
ListWrapper.forEach(dirBindings, dirBinding => {
|
||||
dirBindings.forEach(dirBinding => {
|
||||
bd.push(ProtoElementInjector._createBindingWithVisibility(firstBindingIsComponent, dirBinding,
|
||||
dirBindings, dirBinding));
|
||||
});
|
||||
}
|
||||
|
||||
private static _createBindingsWithVisibility(dirBindings: ResolvedBinding[],
|
||||
bd: BindingWithVisibility[],
|
||||
firstBindingIsComponent: boolean) {
|
||||
ListWrapper.forEach(dirBindings, dirBinding => {
|
||||
ListWrapper.forEach(dirBinding.resolvedBindings, b => {
|
||||
bd.push(ProtoElementInjector._createBindingWithVisibility(firstBindingIsComponent,
|
||||
dirBinding, dirBindings, b));
|
||||
});
|
||||
private static _createBindingsWithVisibility(dirBindings: DirectiveBinding[],
|
||||
bd: BindingWithVisibility[]) {
|
||||
var bindingsFromAllDirectives = [];
|
||||
dirBindings.forEach(dirBinding => {
|
||||
bindingsFromAllDirectives =
|
||||
ListWrapper.concat(bindingsFromAllDirectives, dirBinding.bindings);
|
||||
});
|
||||
|
||||
var resolved = Injector.resolve(bindingsFromAllDirectives);
|
||||
resolved.forEach(b => bd.push(new BindingWithVisibility(b, Visibility.Public)));
|
||||
}
|
||||
|
||||
private static _createBindingWithVisibility(firstBindingIsComponent, dirBinding, dirBindings,
|
||||
binding) {
|
||||
private static _createBindingWithVisibility(firstBindingIsComponent: boolean,
|
||||
dirBinding: DirectiveBinding,
|
||||
dirBindings: DirectiveBinding[],
|
||||
binding: ResolvedBinding) {
|
||||
var isComponent = firstBindingIsComponent && dirBindings[0] === dirBinding;
|
||||
return new BindingWithVisibility(binding,
|
||||
isComponent ? Visibility.PublicAndPrivate : Visibility.Public);
|
||||
}
|
||||
|
||||
private static _createViewBindingsWithVisibility(bindings: ResolvedBinding[],
|
||||
private static _createViewBindingsWithVisibility(dirBindings: DirectiveBinding[],
|
||||
bd: BindingWithVisibility[]) {
|
||||
var db = <DirectiveBinding>bindings[0];
|
||||
ListWrapper.forEach(db.resolvedViewBindings,
|
||||
b => bd.push(new BindingWithVisibility(b, Visibility.Private)));
|
||||
var resolvedViewBindings = Injector.resolve(dirBindings[0].viewBindings);
|
||||
resolvedViewBindings.forEach(b => bd.push(new BindingWithVisibility(b, Visibility.Private)));
|
||||
}
|
||||
|
||||
|
||||
|
@ -852,7 +849,6 @@ interface _ElementInjectorStrategy {
|
|||
isComponentKey(key: Key): boolean;
|
||||
buildQueries(): void;
|
||||
addDirectivesMatchingQuery(q: QueryMetadata, res: any[]): void;
|
||||
getComponentBinding(): DirectiveBinding;
|
||||
hydrate(): void;
|
||||
dehydrate(): void;
|
||||
}
|
||||
|
@ -953,34 +949,44 @@ class ElementInjectorInlineStrategy implements _ElementInjectorStrategy {
|
|||
var p = this.injectorStrategy.protoStrategy;
|
||||
|
||||
if (p.binding0 instanceof DirectiveBinding) {
|
||||
this._ei._buildQueriesForDeps(<DirectiveDependency[]>p.binding0.dependencies);
|
||||
this._ei._buildQueriesForDeps(
|
||||
<DirectiveDependency[]>p.binding0.resolvedFactories[0].dependencies);
|
||||
}
|
||||
if (p.binding1 instanceof DirectiveBinding) {
|
||||
this._ei._buildQueriesForDeps(<DirectiveDependency[]>p.binding1.dependencies);
|
||||
this._ei._buildQueriesForDeps(
|
||||
<DirectiveDependency[]>p.binding1.resolvedFactories[0].dependencies);
|
||||
}
|
||||
if (p.binding2 instanceof DirectiveBinding) {
|
||||
this._ei._buildQueriesForDeps(<DirectiveDependency[]>p.binding2.dependencies);
|
||||
this._ei._buildQueriesForDeps(
|
||||
<DirectiveDependency[]>p.binding2.resolvedFactories[0].dependencies);
|
||||
}
|
||||
if (p.binding3 instanceof DirectiveBinding) {
|
||||
this._ei._buildQueriesForDeps(<DirectiveDependency[]>p.binding3.dependencies);
|
||||
this._ei._buildQueriesForDeps(
|
||||
<DirectiveDependency[]>p.binding3.resolvedFactories[0].dependencies);
|
||||
}
|
||||
if (p.binding4 instanceof DirectiveBinding) {
|
||||
this._ei._buildQueriesForDeps(<DirectiveDependency[]>p.binding4.dependencies);
|
||||
this._ei._buildQueriesForDeps(
|
||||
<DirectiveDependency[]>p.binding4.resolvedFactories[0].dependencies);
|
||||
}
|
||||
if (p.binding5 instanceof DirectiveBinding) {
|
||||
this._ei._buildQueriesForDeps(<DirectiveDependency[]>p.binding5.dependencies);
|
||||
this._ei._buildQueriesForDeps(
|
||||
<DirectiveDependency[]>p.binding5.resolvedFactories[0].dependencies);
|
||||
}
|
||||
if (p.binding6 instanceof DirectiveBinding) {
|
||||
this._ei._buildQueriesForDeps(<DirectiveDependency[]>p.binding6.dependencies);
|
||||
this._ei._buildQueriesForDeps(
|
||||
<DirectiveDependency[]>p.binding6.resolvedFactories[0].dependencies);
|
||||
}
|
||||
if (p.binding7 instanceof DirectiveBinding) {
|
||||
this._ei._buildQueriesForDeps(<DirectiveDependency[]>p.binding7.dependencies);
|
||||
this._ei._buildQueriesForDeps(
|
||||
<DirectiveDependency[]>p.binding7.resolvedFactories[0].dependencies);
|
||||
}
|
||||
if (p.binding8 instanceof DirectiveBinding) {
|
||||
this._ei._buildQueriesForDeps(<DirectiveDependency[]>p.binding8.dependencies);
|
||||
this._ei._buildQueriesForDeps(
|
||||
<DirectiveDependency[]>p.binding8.resolvedFactories[0].dependencies);
|
||||
}
|
||||
if (p.binding9 instanceof DirectiveBinding) {
|
||||
this._ei._buildQueriesForDeps(<DirectiveDependency[]>p.binding9.dependencies);
|
||||
this._ei._buildQueriesForDeps(
|
||||
<DirectiveDependency[]>p.binding9.resolvedFactories[0].dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1029,11 +1035,6 @@ class ElementInjectorInlineStrategy implements _ElementInjectorStrategy {
|
|||
list.push(i.obj9);
|
||||
}
|
||||
}
|
||||
|
||||
getComponentBinding(): DirectiveBinding {
|
||||
var p = this.injectorStrategy.protoStrategy;
|
||||
return <DirectiveBinding>p.binding0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1086,7 +1087,8 @@ class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy {
|
|||
|
||||
for (var i = 0; i < p.bindings.length; i++) {
|
||||
if (p.bindings[i] instanceof DirectiveBinding) {
|
||||
this._ei._buildQueriesForDeps(<DirectiveDependency[]>p.bindings[i].dependencies);
|
||||
this._ei._buildQueriesForDeps(
|
||||
<DirectiveDependency[]>p.bindings[i].resolvedFactory.dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1104,11 +1106,6 @@ class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
getComponentBinding(): DirectiveBinding {
|
||||
var p = this.injectorStrategy.protoStrategy;
|
||||
return <DirectiveBinding>p.bindings[0];
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryError extends BaseException {
|
||||
|
|
|
@ -6,7 +6,8 @@ import {
|
|||
CONST_EXPR,
|
||||
BaseException,
|
||||
stringify,
|
||||
isArray
|
||||
isArray,
|
||||
normalizeBool
|
||||
} from 'angular2/src/core/facade/lang';
|
||||
import {MapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
|
@ -20,7 +21,11 @@ import {
|
|||
SkipSelfMetadata,
|
||||
DependencyMetadata
|
||||
} from './metadata';
|
||||
import {NoAnnotationError} from './exceptions';
|
||||
import {
|
||||
NoAnnotationError,
|
||||
MixingMultiBindingsWithRegularBindings,
|
||||
InvalidBindingError
|
||||
} from './exceptions';
|
||||
import {resolveForwardRef} from './forward_ref';
|
||||
|
||||
/**
|
||||
|
@ -174,46 +179,52 @@ export class Binding {
|
|||
* expect(injector.get(String)).toEqual('Value: 3');
|
||||
* ```
|
||||
*/
|
||||
dependencies: any[];
|
||||
dependencies: Object[];
|
||||
|
||||
constructor(
|
||||
token,
|
||||
{toClass, toValue, toAlias, toFactory, deps}:
|
||||
{toClass?: Type, toValue?: any, toAlias?: any, toFactory?: Function, deps?: any[]}) {
|
||||
_multi: boolean;
|
||||
|
||||
constructor(token, {toClass, toValue, toAlias, toFactory, deps, multi}: {
|
||||
toClass?: Type,
|
||||
toValue?: any,
|
||||
toAlias?: any,
|
||||
toFactory?: Function,
|
||||
deps?: Object[],
|
||||
multi?: boolean
|
||||
}) {
|
||||
this.token = token;
|
||||
this.toClass = toClass;
|
||||
this.toValue = toValue;
|
||||
this.toAlias = toAlias;
|
||||
this.toFactory = toFactory;
|
||||
this.dependencies = deps;
|
||||
this._multi = multi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@link Binding} into {@link ResolvedBinding}.
|
||||
* Used to create multiple bindings matching the same token.
|
||||
*
|
||||
* {@link Injector} internally only uses {@link ResolvedBinding}, {@link Binding} contains
|
||||
* convenience binding syntax.
|
||||
* ## Example
|
||||
*
|
||||
* ```javascript
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* new Binding("Strings", { toValue: "String1", multi: true}),
|
||||
* new Binding("Strings", { toValue: "String2", multi: true})
|
||||
* ]);
|
||||
*
|
||||
* expect(injector.get("Strings")).toEqual(["String1", "String2"]);
|
||||
* ```
|
||||
*
|
||||
* Multi bindings and regular bindings cannot be mixed. The following
|
||||
* will throw an exception:
|
||||
*
|
||||
* ```javascript
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* new Binding("Strings", { toValue: "String1", multi: true}),
|
||||
* new Binding("Strings", { toValue: "String2"})
|
||||
* ]);
|
||||
* ```
|
||||
*/
|
||||
resolve(): ResolvedBinding {
|
||||
var factoryFn: Function;
|
||||
var resolvedDeps;
|
||||
if (isPresent(this.toClass)) {
|
||||
var toClass = resolveForwardRef(this.toClass);
|
||||
factoryFn = reflector.factory(toClass);
|
||||
resolvedDeps = _dependenciesFor(toClass);
|
||||
} else if (isPresent(this.toAlias)) {
|
||||
factoryFn = (aliasInstance) => aliasInstance;
|
||||
resolvedDeps = [Dependency.fromKey(Key.get(this.toAlias))];
|
||||
} else if (isPresent(this.toFactory)) {
|
||||
factoryFn = this.toFactory;
|
||||
resolvedDeps = _constructDependencies(this.toFactory, this.dependencies);
|
||||
} else {
|
||||
factoryFn = () => this.toValue;
|
||||
resolvedDeps = _EMPTY_LIST;
|
||||
}
|
||||
|
||||
return new ResolvedBinding(Key.get(this.token), factoryFn, resolvedDeps);
|
||||
}
|
||||
get multi(): boolean { return normalizeBool(this._multi); }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,6 +241,17 @@ export class ResolvedBinding {
|
|||
*/
|
||||
public key: Key,
|
||||
|
||||
/**
|
||||
* Factory function which can return an instance of an object represented by a key.
|
||||
*/
|
||||
public resolvedFactories: ResolvedFactory[],
|
||||
|
||||
public multiBinding: boolean) {}
|
||||
get resolvedFactory(): ResolvedFactory { return this.resolvedFactories[0]; }
|
||||
}
|
||||
|
||||
export class ResolvedFactory {
|
||||
constructor(
|
||||
/**
|
||||
* Factory function which can return an instance of an object represented by a key.
|
||||
*/
|
||||
|
@ -370,6 +392,126 @@ export class BindingBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a single binding.
|
||||
*/
|
||||
export function resolveFactory(binding: Binding): ResolvedFactory {
|
||||
var factoryFn: Function;
|
||||
var resolvedDeps;
|
||||
if (isPresent(binding.toClass)) {
|
||||
var toClass = resolveForwardRef(binding.toClass);
|
||||
factoryFn = reflector.factory(toClass);
|
||||
resolvedDeps = _dependenciesFor(toClass);
|
||||
} else if (isPresent(binding.toAlias)) {
|
||||
factoryFn = (aliasInstance) => aliasInstance;
|
||||
resolvedDeps = [Dependency.fromKey(Key.get(binding.toAlias))];
|
||||
} else if (isPresent(binding.toFactory)) {
|
||||
factoryFn = binding.toFactory;
|
||||
resolvedDeps = _constructDependencies(binding.toFactory, binding.dependencies);
|
||||
} else {
|
||||
factoryFn = () => binding.toValue;
|
||||
resolvedDeps = _EMPTY_LIST;
|
||||
}
|
||||
return new ResolvedFactory(factoryFn, resolvedDeps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@link Binding} into {@link ResolvedBinding}.
|
||||
*
|
||||
* {@link Injector} internally only uses {@link ResolvedBinding}, {@link Binding} contains
|
||||
* convenience binding syntax.
|
||||
*/
|
||||
export function resolveBinding(binding: Binding): ResolvedBinding {
|
||||
return new ResolvedBinding(Key.get(binding.token), [resolveFactory(binding)], false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a list of Bindings.
|
||||
*/
|
||||
export function resolveBindings(bindings: Array<Type | Binding | any[]>): ResolvedBinding[] {
|
||||
var normalized = _createListOfBindings(_normalizeBindings(bindings, new Map()));
|
||||
return normalized.map(b => {
|
||||
if (b instanceof _NormalizedBinding) {
|
||||
return new ResolvedBinding(b.key, [b.resolvedFactory], false);
|
||||
|
||||
} else {
|
||||
var arr = <_NormalizedBinding[]>b;
|
||||
return new ResolvedBinding(arr[0].key, arr.map(_ => _.resolvedFactory), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The algorithm works as follows:
|
||||
*
|
||||
* [Binding] -> [_NormalizedBinding|[_NormalizedBinding]] -> [ResolvedBinding]
|
||||
*
|
||||
* _NormalizedBinding is essentially a resolved binding before it was grouped by key.
|
||||
*/
|
||||
class _NormalizedBinding {
|
||||
constructor(public key: Key, public resolvedFactory: ResolvedFactory) {}
|
||||
}
|
||||
|
||||
function _createListOfBindings(flattenedBindings: Map<number, any>): any[] {
|
||||
return MapWrapper.values(flattenedBindings);
|
||||
}
|
||||
|
||||
function _normalizeBindings(bindings: Array<Type | Binding | any[]>,
|
||||
res: Map<number, _NormalizedBinding | _NormalizedBinding[]>):
|
||||
Map<number, _NormalizedBinding | _NormalizedBinding[]> {
|
||||
ListWrapper.forEach(bindings, (b) => {
|
||||
var key, factory, normalized;
|
||||
|
||||
if (b instanceof Type) {
|
||||
_normalizeBinding(bind(b).toClass(b), res);
|
||||
|
||||
} else if (b instanceof Binding) {
|
||||
_normalizeBinding(b, res);
|
||||
|
||||
} else if (b instanceof Array) {
|
||||
_normalizeBindings(b, res);
|
||||
|
||||
} else if (b instanceof BindingBuilder) {
|
||||
throw new InvalidBindingError(b.token);
|
||||
|
||||
} else {
|
||||
throw new InvalidBindingError(b);
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function _normalizeBinding(b: Binding, res: Map<number, _NormalizedBinding | _NormalizedBinding[]>):
|
||||
void {
|
||||
var key = Key.get(b.token);
|
||||
var factory = resolveFactory(b);
|
||||
var normalized = new _NormalizedBinding(key, factory);
|
||||
|
||||
if (b.multi) {
|
||||
var existingBinding = res.get(key.id);
|
||||
|
||||
if (existingBinding instanceof Array) {
|
||||
existingBinding.push(normalized);
|
||||
|
||||
} else if (isBlank(existingBinding)) {
|
||||
res.set(key.id, [normalized]);
|
||||
|
||||
} else {
|
||||
throw new MixingMultiBindingsWithRegularBindings(existingBinding, b);
|
||||
}
|
||||
|
||||
} else {
|
||||
var existingBinding = res.get(key.id);
|
||||
|
||||
if (existingBinding instanceof Array) {
|
||||
throw new MixingMultiBindingsWithRegularBindings(existingBinding, b);
|
||||
}
|
||||
|
||||
res.set(key.id, normalized);
|
||||
}
|
||||
}
|
||||
|
||||
function _constructDependencies(factoryFunction: Function, dependencies: any[]): Dependency[] {
|
||||
if (isBlank(dependencies)) {
|
||||
return _dependenciesFor(factoryFunction);
|
||||
|
|
|
@ -167,3 +167,17 @@ export class OutOfBoundsError extends BaseException {
|
|||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when a multi binding and a regular binding are bound to the same token.
|
||||
*/
|
||||
export class MixingMultiBindingsWithRegularBindings extends BaseException {
|
||||
message: string;
|
||||
constructor(binding1, binding2) {
|
||||
super();
|
||||
this.message = "Cannot mix multi bindings and regular bindings, got: " + binding1.toString() +
|
||||
" " + binding2.toString();
|
||||
}
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
|
@ -1,17 +1,28 @@
|
|||
import {Map, MapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {ResolvedBinding, Binding, Dependency, BindingBuilder, bind} from './binding';
|
||||
import {
|
||||
ResolvedBinding,
|
||||
Binding,
|
||||
Dependency,
|
||||
BindingBuilder,
|
||||
ResolvedFactory,
|
||||
bind,
|
||||
resolveBindings
|
||||
} from './binding';
|
||||
import {
|
||||
AbstractBindingError,
|
||||
NoBindingError,
|
||||
CyclicDependencyError,
|
||||
InstantiationError,
|
||||
InvalidBindingError,
|
||||
OutOfBoundsError
|
||||
OutOfBoundsError,
|
||||
MixingMultiBindingsWithRegularBindings
|
||||
} from './exceptions';
|
||||
import {FunctionWrapper, Type, isPresent, isBlank, CONST_EXPR} from 'angular2/src/core/facade/lang';
|
||||
import {Key} from './key';
|
||||
import {resolveForwardRef} from './forward_ref';
|
||||
import {SelfMetadata, HostMetadata, SkipSelfMetadata} from './metadata';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
|
||||
|
||||
// Threshold for the dynamic version
|
||||
const _MAX_CONSTRUCTION_COUNTER = 10;
|
||||
|
@ -428,9 +439,7 @@ export class Injector {
|
|||
* `fromResolvedBindings` and `createChildFromResolved`.
|
||||
*/
|
||||
static resolve(bindings: Array<Type | Binding | any[]>): ResolvedBinding[] {
|
||||
var resolvedBindings = _resolveBindings(bindings);
|
||||
var flatten = _flattenBindings(resolvedBindings, new Map());
|
||||
return _createListOfBindings(flatten);
|
||||
return resolveBindings(bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -577,19 +586,32 @@ export class Injector {
|
|||
* @returns an object created using binding.
|
||||
*/
|
||||
instantiateResolved(binding: ResolvedBinding): any {
|
||||
return this._instantiate(binding, Visibility.PublicAndPrivate);
|
||||
return this._instantiateBinding(binding, Visibility.PublicAndPrivate);
|
||||
}
|
||||
|
||||
_new(binding: ResolvedBinding, visibility: Visibility): any {
|
||||
if (this._constructionCounter++ > this._strategy.getMaxNumberOfObjects()) {
|
||||
throw new CyclicDependencyError(this, binding.key);
|
||||
}
|
||||
return this._instantiate(binding, visibility);
|
||||
return this._instantiateBinding(binding, visibility);
|
||||
}
|
||||
|
||||
private _instantiate(binding: ResolvedBinding, visibility: Visibility): any {
|
||||
var factory = binding.factory;
|
||||
var deps = binding.dependencies;
|
||||
private _instantiateBinding(binding: ResolvedBinding, visibility: Visibility): any {
|
||||
if (binding.multiBinding) {
|
||||
var res = ListWrapper.createFixedSize(binding.resolvedFactories.length);
|
||||
for (var i = 0; i < binding.resolvedFactories.length; ++i) {
|
||||
res[i] = this._instantiate(binding, binding.resolvedFactories[i], visibility);
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
return this._instantiate(binding, binding.resolvedFactories[0], visibility);
|
||||
}
|
||||
}
|
||||
|
||||
private _instantiate(binding: ResolvedBinding, resolvedFactory: ResolvedFactory,
|
||||
visibility: Visibility): any {
|
||||
var factory = resolvedFactory.factory;
|
||||
var deps = resolvedFactory.dependencies;
|
||||
var length = deps.length;
|
||||
|
||||
var d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18, d19;
|
||||
|
@ -801,45 +823,6 @@ export class Injector {
|
|||
var INJECTOR_KEY = Key.get(Injector);
|
||||
|
||||
|
||||
function _resolveBindings(bindings: Array<Type | Binding | any[]>): ResolvedBinding[] {
|
||||
var resolvedList = ListWrapper.createFixedSize(bindings.length);
|
||||
for (var i = 0; i < bindings.length; i++) {
|
||||
var unresolved = resolveForwardRef(bindings[i]);
|
||||
var resolved;
|
||||
if (unresolved instanceof ResolvedBinding) {
|
||||
resolved = unresolved; // ha-ha! I'm easily amused
|
||||
} else if (unresolved instanceof Type) {
|
||||
resolved = bind(unresolved).toClass(unresolved).resolve();
|
||||
} else if (unresolved instanceof Binding) {
|
||||
resolved = unresolved.resolve();
|
||||
} else if (unresolved instanceof Array) {
|
||||
resolved = _resolveBindings(unresolved);
|
||||
} else if (unresolved instanceof BindingBuilder) {
|
||||
throw new InvalidBindingError(unresolved.token);
|
||||
} else {
|
||||
throw new InvalidBindingError(unresolved);
|
||||
}
|
||||
resolvedList[i] = resolved;
|
||||
}
|
||||
return resolvedList;
|
||||
}
|
||||
|
||||
function _createListOfBindings(flattenedBindings: Map<number, ResolvedBinding>): ResolvedBinding[] {
|
||||
return MapWrapper.values(flattenedBindings);
|
||||
}
|
||||
|
||||
function _flattenBindings(bindings: Array<ResolvedBinding | any[]>,
|
||||
res: Map<number, ResolvedBinding>): Map<number, ResolvedBinding> {
|
||||
ListWrapper.forEach(bindings, function(b) {
|
||||
if (b instanceof ResolvedBinding) {
|
||||
res.set(b.key.id, b);
|
||||
} else if (b instanceof Array) {
|
||||
_flattenBindings(b, res);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
function _mapBindings(injector: Injector, fn: Function): any[] {
|
||||
var res = [];
|
||||
for (var i = 0; i < injector._proto.numberOfBindings; ++i) {
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import {Type} from 'angular2/src/core/facade/lang';
|
||||
import {Key, Dependency, ResolvedBinding, Binding} from 'angular2/di';
|
||||
import {Key, ResolvedBinding, Binding} from 'angular2/di';
|
||||
import {ResolvedFactory, resolveBinding} from 'angular2/src/core/di/binding';
|
||||
import {PipeMetadata} from '../metadata/directives';
|
||||
|
||||
export class PipeBinding extends ResolvedBinding {
|
||||
constructor(public name: string, key: Key, factory: Function, dependencies: Dependency[]) {
|
||||
super(key, factory, dependencies);
|
||||
constructor(public name: string, key: Key, resolvedFactories: ResolvedFactory[],
|
||||
multiBinding: boolean) {
|
||||
super(key, resolvedFactories, multiBinding);
|
||||
}
|
||||
|
||||
static createFromType(type: Type, metadata: PipeMetadata): PipeBinding {
|
||||
var binding = new Binding(type, {toClass: type});
|
||||
var rb = binding.resolve();
|
||||
return new PipeBinding(metadata.name, rb.key, rb.factory, rb.dependencies);
|
||||
var rb = resolveBinding(binding);
|
||||
return new PipeBinding(metadata.name, rb.key, rb.resolvedFactories, rb.multiBinding);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -676,7 +676,7 @@ export function main() {
|
|||
`No provider for service! (${stringify(NeedsService) } -> service)`));
|
||||
});
|
||||
|
||||
it("should instantiate directives that depend on bindings bindings of other directives", () => {
|
||||
it("should instantiate directives that depend on bindings of other directives", () => {
|
||||
var shadowInj = hostShadowInjectors(
|
||||
ListWrapper.concat([DirectiveBinding.createFromType(SimpleDirective, new ComponentMetadata({
|
||||
bindings: [bind('service').toValue('hostService')]})
|
||||
|
@ -723,8 +723,30 @@ export function main() {
|
|||
expect(inj.get(NeedsService).service).toEqual('viewService');
|
||||
});
|
||||
|
||||
it("should not instantiate a directive in a view that has an ancestor dependency on bindings"+
|
||||
" bindings of a decorator directive", () => {
|
||||
it("should prioritize directive bindings over component bindings", () => {
|
||||
var component = DirectiveBinding.createFromType(NeedsService, new ComponentMetadata({
|
||||
bindings: [bind('service').toValue('compService')]}));
|
||||
var directive = DirectiveBinding.createFromType(SomeOtherDirective, new DirectiveMetadata({
|
||||
bindings: [bind('service').toValue('dirService')]}));
|
||||
var inj = injector(ListWrapper.concat([component, directive], extraBindings), null, true);
|
||||
expect(inj.get(NeedsService).service).toEqual('dirService');
|
||||
});
|
||||
|
||||
it("should not instantiate a directive in a view that has a host dependency on bindings"+
|
||||
" of the component", () => {
|
||||
expect(() => {
|
||||
hostShadowInjectors(
|
||||
ListWrapper.concat([
|
||||
DirectiveBinding.createFromType(SomeOtherDirective, new DirectiveMetadata({
|
||||
bindings: [bind('service').toValue('hostService')]})
|
||||
)], extraBindings),
|
||||
ListWrapper.concat([NeedsServiceFromHost], extraBindings)
|
||||
);
|
||||
}).toThrowError(new RegExp("No provider for service!"));
|
||||
});
|
||||
|
||||
it("should not instantiate a directive in a view that has a host dependency on bindings"+
|
||||
" of a decorator directive", () => {
|
||||
expect(() => {
|
||||
hostShadowInjectors(
|
||||
ListWrapper.concat([
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
createVariableLocations
|
||||
} from 'angular2/src/core/compiler/proto_view_factory';
|
||||
import {Component, Directive} from 'angular2/metadata';
|
||||
import {Key} from 'angular2/di';
|
||||
import {Key, Binding} from 'angular2/di';
|
||||
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
||||
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
|
||||
import {
|
||||
|
@ -222,7 +222,7 @@ export function main() {
|
|||
}
|
||||
|
||||
function directiveBinding({metadata}: {metadata?: any} = {}) {
|
||||
return new DirectiveBinding(Key.get("dummy"), null, [], [], [], metadata);
|
||||
return new DirectiveBinding(Key.get("dummy"), null, null, metadata, [], []);
|
||||
}
|
||||
|
||||
function createRenderProtoView(elementBinders = null, type: ViewType = null,
|
||||
|
|
|
@ -17,7 +17,8 @@ import {
|
|||
Optional,
|
||||
Inject,
|
||||
BindingWithVisibility,
|
||||
Visibility
|
||||
Visibility,
|
||||
Binding
|
||||
} from 'angular2/di';
|
||||
|
||||
import {InjectorInlineStrategy, InjectorDynamicStrategy} from 'angular2/src/core/di/injector';
|
||||
|
@ -104,7 +105,6 @@ export function main() {
|
|||
bindings: dynamicBindings,
|
||||
strategyClass: InjectorDynamicStrategy
|
||||
}].forEach((context) => {
|
||||
|
||||
function createInjector(bindings: any[], dependencyProvider = null) {
|
||||
return Injector.resolveAndCreate(bindings.concat(context['bindings']), dependencyProvider);
|
||||
}
|
||||
|
@ -186,6 +186,28 @@ export function main() {
|
|||
expect(car).toBe(sportsCar);
|
||||
});
|
||||
|
||||
it('should support multibindings', () => {
|
||||
var injector = createInjector([
|
||||
Engine,
|
||||
new Binding(Car, {toClass: SportsCar, multi: true}),
|
||||
new Binding(Car, {toClass: CarWithOptionalEngine, multi: true})
|
||||
]);
|
||||
|
||||
var cars = injector.get(Car);
|
||||
expect(cars.length).toEqual(2);
|
||||
expect(cars[0]).toBeAnInstanceOf(SportsCar);
|
||||
expect(cars[1]).toBeAnInstanceOf(CarWithOptionalEngine);
|
||||
});
|
||||
|
||||
it('should support multibindings that are created using toAlias', () => {
|
||||
var injector = createInjector(
|
||||
[Engine, SportsCar, new Binding(Car, {toAlias: SportsCar, multi: true})]);
|
||||
|
||||
var cars = injector.get(Car);
|
||||
expect(cars.length).toEqual(1);
|
||||
expect(cars[0]).toBe(injector.get(SportsCar));
|
||||
});
|
||||
|
||||
it('should throw when the aliased binding does not exist', () => {
|
||||
var injector = createInjector([bind('car').toAlias(SportsCar)]);
|
||||
var e = `No provider for ${stringify(SportsCar)}! (car -> ${stringify(SportsCar)})`;
|
||||
|
@ -345,7 +367,8 @@ export function main() {
|
|||
|
||||
expect(injector.get(Car).engine).toEqual(e);
|
||||
expect(depProvider.spy("getDependency"))
|
||||
.toHaveBeenCalledWith(injector, bindings[0], bindings[0].dependencies[0]);
|
||||
.toHaveBeenCalledWith(injector, bindings[0],
|
||||
bindings[0].resolvedFactories[0].dependencies[0]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -544,7 +567,6 @@ export function main() {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
describe('resolve', () => {
|
||||
it('should resolve and flatten', () => {
|
||||
var bindings = Injector.resolve([Engine, [BrokenEngine]]);
|
||||
|
@ -554,6 +576,36 @@ export function main() {
|
|||
});
|
||||
});
|
||||
|
||||
it("should support multi bindings", () => {
|
||||
var binding = Injector.resolve([
|
||||
new Binding(Engine, {toClass: BrokenEngine, multi: true}),
|
||||
new Binding(Engine, {toClass: TurboEngine, multi: true})
|
||||
])[0];
|
||||
|
||||
expect(binding.key.token).toBe(Engine);
|
||||
expect(binding.multiBinding).toEqual(true);
|
||||
expect(binding.resolvedFactories.length).toEqual(2);
|
||||
});
|
||||
|
||||
it("should support multi bindings with only one binding", () => {
|
||||
var binding =
|
||||
Injector.resolve([new Binding(Engine, {toClass: BrokenEngine, multi: true})])[0];
|
||||
|
||||
expect(binding.key.token).toBe(Engine);
|
||||
expect(binding.multiBinding).toEqual(true);
|
||||
expect(binding.resolvedFactories.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("should throw when mixing multi bindings with regular bindings", () => {
|
||||
expect(() => {
|
||||
Injector.resolve([new Binding(Engine, {toClass: BrokenEngine, multi: true}), Engine]);
|
||||
}).toThrowErrorWith("Cannot mix multi bindings and regular bindings");
|
||||
|
||||
expect(() => {
|
||||
Injector.resolve([Engine, new Binding(Engine, {toClass: BrokenEngine, multi: true})]);
|
||||
}).toThrowErrorWith("Cannot mix multi bindings and regular bindings");
|
||||
});
|
||||
|
||||
it('should resolve forward references', () => {
|
||||
var bindings = Injector.resolve([
|
||||
forwardRef(() => Engine),
|
||||
|
@ -565,9 +617,9 @@ export function main() {
|
|||
var brokenEngineBinding = bindings[1];
|
||||
var stringBinding = bindings[2];
|
||||
|
||||
expect(engineBinding.factory() instanceof Engine).toBe(true);
|
||||
expect(brokenEngineBinding.factory() instanceof Engine).toBe(true);
|
||||
expect(stringBinding.dependencies[0].key).toEqual(Key.get(Engine));
|
||||
expect(engineBinding.resolvedFactories[0].factory() instanceof Engine).toBe(true);
|
||||
expect(brokenEngineBinding.resolvedFactories[0].factory() instanceof Engine).toBe(true);
|
||||
expect(stringBinding.resolvedFactories[0].dependencies[0].key).toEqual(Key.get(Engine));
|
||||
});
|
||||
|
||||
it('should support overriding factory dependencies with dependency annotations', () => {
|
||||
|
@ -576,10 +628,12 @@ export function main() {
|
|||
.toFactory((e) => "result",
|
||||
[[new InjectMetadata("dep"), new CustomDependencyMetadata()]])
|
||||
]);
|
||||
|
||||
var binding = bindings[0];
|
||||
|
||||
expect(binding.dependencies[0].key.token).toEqual("dep");
|
||||
expect(binding.dependencies[0].properties).toEqual([new CustomDependencyMetadata()]);
|
||||
expect(binding.resolvedFactories[0].dependencies[0].key.token).toEqual("dep");
|
||||
expect(binding.resolvedFactories[0].dependencies[0].properties)
|
||||
.toEqual([new CustomDependencyMetadata()]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -20,8 +20,7 @@ export function main() {
|
|||
it('should create a binding out of a type', () => {
|
||||
var binding = PipeBinding.createFromType(MyPipe, new Pipe({name: 'my-pipe'}));
|
||||
expect(binding.name).toEqual('my-pipe');
|
||||
expect(binding.factory()).toBeAnInstanceOf(MyPipe);
|
||||
expect(binding.dependencies.length).toEqual(0);
|
||||
expect(binding.key.token).toEqual(MyPipe);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue