perf(view): use pre-resolved bindings for child injector init

Creating a child injector from pre-resolved bindings (if any) is an
order of magnitude faster.
This commit is contained in:
Yegor Jbanov 2015-04-11 16:57:42 -07:00
parent c05bad381c
commit 308823b6ea
8 changed files with 62 additions and 24 deletions

View File

@ -1,5 +1,7 @@
import {Type} from 'angular2/src/facade/lang';
import {List} from 'angular2/src/facade/collection';
import {Directive} from 'angular2/src/core/annotations/annotations'
import {ResolvedBinding} from 'angular2/di';
/**
* Combination of a type with the Directive annotation
@ -7,9 +9,11 @@ import {Directive} from 'angular2/src/core/annotations/annotations'
export class DirectiveMetadata {
type:Type;
annotation:Directive;
resolvedInjectables:List<ResolvedBinding>;
constructor(type:Type, annotation:Directive) {
constructor(type:Type, annotation:Directive, resolvedInjectables:List<ResolvedBinding>) {
this.annotation = annotation;
this.type = type;
this.resolvedInjectables = resolvedInjectables;
}
}

View File

@ -1,6 +1,6 @@
import {Injectable} from 'angular2/di';
import {Injectable, Injector} from 'angular2/di';
import {Type, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
import {Directive} from '../annotations/annotations';
import {Directive, Component} from '../annotations/annotations';
import {DirectiveMetadata} from './directive_metadata';
import {reflector} from 'angular2/src/reflection/reflection';
@ -13,7 +13,11 @@ export class DirectiveMetadataReader {
var annotation = annotations[i];
if (annotation instanceof Directive) {
return new DirectiveMetadata(type, annotation);
var resolvedInjectables = null;
if (annotation instanceof Component && isPresent(annotation.injectables)) {
resolvedInjectables = Injector.resolve(annotation.injectables);
}
return new DirectiveMetadata(type, annotation, resolvedInjectables);
}
}
}

View File

@ -1,7 +1,8 @@
import {Key, Injector, Injectable} from 'angular2/di'
import {Key, Injector, Injectable, ResolvedBinding} from 'angular2/di'
import {Compiler} from './compiler';
import {DirectiveMetadataReader} from './directive_metadata_reader';
import {Type, BaseException, stringify, isPresent} from 'angular2/src/facade/lang';
import {List} from 'angular2/src/facade/collection';
import {Promise} from 'angular2/src/facade/async';
import {Component} from 'angular2/src/core/annotations/annotations';
import {ViewFactory} from 'angular2/src/core/compiler/view_factory';
@ -34,15 +35,15 @@ export class DynamicComponentLoader {
loadIntoExistingLocation(type:Type, location:ElementRef, injector:Injector = null):Promise<ComponentRef> {
this._assertTypeIsComponent(type);
var annotation = this._directiveMetadataReader.read(type).annotation;
var directiveMetadata = this._directiveMetadataReader.read(type);
var inj = this._componentAppInjector(location, injector, annotation.injectables);
var inj = this._componentAppInjector(location, injector, directiveMetadata.resolvedInjectables);
var hostEi = location.elementInjector;
var hostView = location.hostView;
return this._compiler.compile(type).then(componentProtoView => {
var context = hostEi.dynamicallyCreateComponent(type, annotation, inj);
var context = hostEi.dynamicallyCreateComponent(type, directiveMetadata.annotation, inj);
var componentView = this._instantiateAndHydrateView(componentProtoView, injector, hostEi, context);
//TODO(vsavkin): do not use component child views as we need to clear the dynamically created views
@ -78,9 +79,9 @@ export class DynamicComponentLoader {
});
}
_componentAppInjector(location, injector, services) {
_componentAppInjector(location, injector:Injector, resolvedBindings:List<ResolvedBinding>) {
var inj = isPresent(injector) ? injector : location.injector;
return isPresent(services) ? inj.resolveAndCreateChild(services) : inj;
return isPresent(resolvedBindings) ? inj.createChildFromResolved(resolvedBindings) : inj;
}
_instantiateAndHydrateView(protoView, injector, hostElementInjector, context) {

View File

@ -7,7 +7,7 @@ import {EventEmitter, PropertySetter, Attribute, Query} from 'angular2/src/core/
import * as viewModule from 'angular2/src/core/compiler/view';
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {NgElement} from 'angular2/src/core/compiler/ng_element';
import {Directive, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations';
import {Directive, Component, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations';
import {BindingPropagationConfig} from 'angular2/change_detection';
import {QueryList} from './query_list';
@ -283,6 +283,7 @@ export class DirectiveBinding extends ResolvedBinding {
callOnChange:boolean;
callOnAllChangesDone:boolean;
annotation:Directive;
resolvedInjectables:List<ResolvedBinding>;
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, annotation:Directive) {
super(key, factory, dependencies, providedAsPromise);
@ -290,6 +291,9 @@ export class DirectiveBinding extends ResolvedBinding {
this.callOnChange = isPresent(annotation) && annotation.hasLifecycleHook(onChange);
this.callOnAllChangesDone = isPresent(annotation) && annotation.hasLifecycleHook(onAllChangesDone);
this.annotation = annotation;
if (annotation instanceof Component && isPresent(annotation.injectables)) {
this.resolvedInjectables = Injector.resolve(annotation.injectables);
}
}
static createFromBinding(b:Binding, annotation:Directive):DirectiveBinding {

View File

@ -151,9 +151,9 @@ export class AppView {
// shadowDomAppInjector
if (isPresent(componentDirective)) {
var injectables = componentDirective.annotation.injectables;
var injectables = componentDirective.resolvedInjectables;
if (isPresent(injectables))
shadowDomAppInjector = appInjector.resolveAndCreateChild(injectables);
shadowDomAppInjector = appInjector.createChildFromResolved(injectables);
else {
shadowDomAppInjector = appInjector;
}

View File

@ -267,14 +267,12 @@ class _AsyncInjectorStrategy {
}
}
function _createListOfBindings(flattenBindings):List {
function _createListOfBindings(flattenedBindings):List {
var bindings = ListWrapper.createFixedSize(Key.numberOfKeys + 1);
MapWrapper.forEach(flattenBindings, (v, keyId) => bindings[keyId] = v);
MapWrapper.forEach(flattenedBindings, (v, keyId) => bindings[keyId] = v);
return bindings;
}
function _flattenBindings(bindings:List, res:Map) {
ListWrapper.forEach(bindings, function (b) {
if (b instanceof ResolvedBinding) {

View File

@ -1,13 +1,18 @@
import {isPresent} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/test_lib';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {Decorator, Component, Viewport} from 'angular2/src/core/annotations/annotations';
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
import {Injectable, Injector} from 'angular2/di';
@Injectable()
class SomeInjectable {}
@Decorator({selector: 'someDecorator'})
class SomeDecorator {}
@Component({selector: 'someComponent'})
@Component({selector: 'someComponent', injectables: [SomeInjectable]})
class SomeComponent {}
@Viewport({selector: 'someViewport'})
@ -27,19 +32,30 @@ export function main() {
it('should read out the Decorator annotation', () => {
var directiveMetadata = reader.read(SomeDecorator);
expect(directiveMetadata).toEqual(
new DirectiveMetadata(SomeDecorator, new Decorator({selector: 'someDecorator'})));
new DirectiveMetadata(SomeDecorator, new Decorator({selector: 'someDecorator'}), null));
});
it('should read out the Viewport annotation', () => {
var directiveMetadata = reader.read(SomeViewport);
expect(directiveMetadata).toEqual(
new DirectiveMetadata(SomeViewport, new Viewport({selector: 'someViewport'})));
new DirectiveMetadata(SomeViewport, new Viewport({selector: 'someViewport'}), null));
});
it('should read out the Component annotation', () => {
var directiveMetadata = reader.read(SomeComponent);
expect(directiveMetadata).toEqual(
new DirectiveMetadata(SomeComponent, new Component({selector: 'someComponent'})));
var m = reader.read(SomeComponent);
// For some reason `toEqual` fails to compare ResolvedBinding objects.
// Have to decompose and compare.
expect(m.type).toEqual(SomeComponent);
expect(m.annotation)
.toEqual(new Component({selector: 'someComponent', injectables: [SomeInjectable]}));
var resolvedList = ListWrapper.reduce(m.resolvedInjectables, function(prev, elem) {
if (isPresent(elem)) {
ListWrapper.push(prev, elem);
}
return prev;
}, []);
expect(resolvedList.length).toBe(1);
expect(resolvedList[0].key.token).toBe(SomeInjectable);
});
it('should throw if not matching annotation is found', () => {

View File

@ -1,5 +1,6 @@
import {isBlank} from 'angular2/src/facade/lang';
import {describe, ddescribe, it, iit, expect, beforeEach} from 'angular2/test_lib';
import {Injector, Inject, InjectLazy, Optional, bind} from 'angular2/di';
import {Injector, Inject, InjectLazy, Optional, bind, ResolvedBinding} from 'angular2/di';
class Engine {
}
@ -364,5 +365,15 @@ export function main() {
expect(e1).toBe(e2);
});
});
describe('resolve', function() {
it('should resolve and flatten', function() {
var bindings = Injector.resolve([Engine, [BrokenEngine]]);
bindings.forEach(function(b) {
if (isBlank(b)) return; // the result is a sparse array
expect(b instanceof ResolvedBinding).toBe(true);
});
});
});
});
}