refactor(proto_view_factory): expose data for generating change detectors

Also consolidates metadata handling in `ElementInjector`

BREAKING CHANGE:
- renames `DirectiveMetadataReader` into `DirectiveResolver`
  and removes `src/core/compiler/directive_metadata`.

Fixes #1712
Fixes #1713
This commit is contained in:
Tobias Bosch
2015-05-11 17:59:39 -07:00
parent 5114411749
commit ecb068019b
33 changed files with 685 additions and 436 deletions

View File

@ -20,7 +20,7 @@ import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
import {AppProtoView} from 'angular2/src/core/compiler/view';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations';
import {Attribute} from 'angular2/src/core/annotations_impl/di';
import {View} from 'angular2/src/core/annotations_impl/view';
@ -37,10 +37,10 @@ import {RenderCompiler} from 'angular2/src/render/api';
export function main() {
describe('compiler', function() {
var reader, tplResolver, renderCompiler, protoViewFactory, cmpUrlMapper, renderCompileRequests;
var directiveResolver, tplResolver, renderCompiler, protoViewFactory, cmpUrlMapper, renderCompileRequests;
beforeEach(() => {
reader = new DirectiveMetadataReader();
directiveResolver = new DirectiveResolver();
tplResolver = new FakeTemplateResolver();
cmpUrlMapper = new RuntimeComponentUrlMapper();
renderCompiler = new SpyRenderCompiler();
@ -56,7 +56,7 @@ export function main() {
protoViewFactory = new FakeProtoViewFactory(protoViewFactoryResults)
return new Compiler(
reader,
directiveResolver,
new CompilerCache(),
tplResolver,
cmpUrlMapper,
@ -70,7 +70,7 @@ export function main() {
function captureTemplate(template:View):Promise<renderApi.ViewDefinition> {
tplResolver.setView(MainComponent, template);
var compiler = createCompiler([createRenderProtoView()], [createProtoView()]);
var compiler = createCompiler([createRenderProtoView()], [[createProtoView()]]);
return compiler.compile(MainComponent).then( (_) => {
expect(renderCompileRequests.length).toBe(1);
return renderCompileRequests[0];
@ -219,7 +219,7 @@ export function main() {
tplResolver.setView(MainComponent, new View({template: '<div></div>'}));
var renderProtoView = createRenderProtoView();
var expectedProtoView = createProtoView();
var compiler = createCompiler([renderProtoView], [expectedProtoView]);
var compiler = createCompiler([renderProtoView], [[expectedProtoView]]);
compiler.compile(MainComponent).then( (_) => {
var request = protoViewFactory.requests[0];
expect(request[1]).toBe(renderProtoView);
@ -229,7 +229,7 @@ export function main() {
it('should pass the component binding', inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new View({template: '<div></div>'}));
var compiler = createCompiler([createRenderProtoView()], [createProtoView()]);
var compiler = createCompiler([createRenderProtoView()], [[createProtoView()]]);
compiler.compile(MainComponent).then( (_) => {
var request = protoViewFactory.requests[0];
expect(request[0].key.token).toBe(MainComponent);
@ -244,7 +244,7 @@ export function main() {
directives: [SomeDirective]
})
);
var compiler = createCompiler([createRenderProtoView()], [createProtoView()]);
var compiler = createCompiler([createRenderProtoView()], [[createProtoView()]]);
compiler.compile(MainComponent).then( (_) => {
var request = protoViewFactory.requests[0];
var binding = request[2][0];
@ -257,7 +257,7 @@ export function main() {
tplResolver.setView(MainComponent, new View({template: '<div></div>'}));
var renderProtoView = createRenderProtoView();
var expectedProtoView = createProtoView();
var compiler = createCompiler([renderProtoView], [expectedProtoView]);
var compiler = createCompiler([renderProtoView], [[expectedProtoView]]);
compiler.compile(MainComponent).then( (protoViewRef) => {
expect(internalProtoView(protoViewRef)).toBe(expectedProtoView);
async.done();
@ -270,7 +270,7 @@ export function main() {
tplResolver.setView(MainComponent, new View({template: '<div></div>'}));
tplResolver.setView(NestedComponent, new View({template: '<div></div>'}));
var mainProtoView = createProtoView([
createComponentElementBinder(reader, NestedComponent)
createComponentElementBinder(directiveResolver, NestedComponent)
]);
var nestedProtoView = createProtoView();
var compiler = createCompiler(
@ -278,7 +278,7 @@ export function main() {
createRenderProtoView([createRenderComponentElementBinder(0)]),
createRenderProtoView()
],
[mainProtoView, nestedProtoView]
[[mainProtoView], [nestedProtoView]]
);
compiler.compile(MainComponent).then( (protoViewRef) => {
expect(internalProtoView(protoViewRef)).toBe(mainProtoView);
@ -294,7 +294,7 @@ export function main() {
createViewportElementBinder(null)
]);
var viewportProtoView = createProtoView([
createComponentElementBinder(reader, NestedComponent)
createComponentElementBinder(directiveResolver, NestedComponent)
]);
var nestedProtoView = createProtoView();
var compiler = createCompiler(
@ -303,16 +303,15 @@ export function main() {
createRenderViewportElementBinder(
createRenderProtoView([
createRenderComponentElementBinder(0)
])
], renderApi.ProtoViewDto.EMBEDDED_VIEW_TYPE)
)
]),
createRenderProtoView()
],
[mainProtoView, viewportProtoView, nestedProtoView]
[[mainProtoView, viewportProtoView], [nestedProtoView]]
);
compiler.compile(MainComponent).then( (protoViewRef) => {
expect(internalProtoView(protoViewRef)).toBe(mainProtoView);
expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(viewportProtoView);
expect(viewportProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
async.done();
@ -323,7 +322,7 @@ export function main() {
tplResolver.setView(MainComponent, new View({template: '<div></div>'}));
var renderProtoView = createRenderProtoView();
var expectedProtoView = createProtoView();
var compiler = createCompiler([renderProtoView], [expectedProtoView]);
var compiler = createCompiler([renderProtoView], [[expectedProtoView]]);
compiler.compile(MainComponent).then( (protoViewRef) => {
expect(internalProtoView(protoViewRef)).toBe(expectedProtoView);
return compiler.compile(MainComponent);
@ -337,7 +336,7 @@ export function main() {
tplResolver.setView(MainComponent, new View({template: '<div></div>'}));
var renderProtoViewCompleter = PromiseWrapper.completer();
var expectedProtoView = createProtoView();
var compiler = createCompiler([renderProtoViewCompleter.promise], [expectedProtoView]);
var compiler = createCompiler([renderProtoViewCompleter.promise], [[expectedProtoView]]);
renderProtoViewCompleter.resolve(createRenderProtoView());
PromiseWrapper.all([
compiler.compile(MainComponent),
@ -352,13 +351,13 @@ export function main() {
it('should allow recursive components', inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new View({template: '<div></div>'}));
var mainProtoView = createProtoView([
createComponentElementBinder(reader, MainComponent)
createComponentElementBinder(directiveResolver, MainComponent)
]);
var compiler = createCompiler(
[createRenderProtoView([
createRenderComponentElementBinder(0)
])],
[mainProtoView]
[[mainProtoView]]
);
compiler.compile(MainComponent).then( (protoViewRef) => {
expect(internalProtoView(protoViewRef)).toBe(mainProtoView);
@ -370,19 +369,19 @@ export function main() {
it('should create host proto views', inject([AsyncTestCompleter], (async) => {
renderCompiler.spy('compileHost').andCallFake( (componentId) => {
return PromiseWrapper.resolve(
createRenderProtoView([createRenderComponentElementBinder(0)])
createRenderProtoView([createRenderComponentElementBinder(0)], renderApi.ProtoViewDto.HOST_VIEW_TYPE)
);
});
tplResolver.setView(MainComponent, new View({template: '<div></div>'}));
var rootProtoView = createProtoView([
createComponentElementBinder(reader, MainComponent)
createComponentElementBinder(directiveResolver, MainComponent)
]);
var mainProtoView = createProtoView();
var compiler = createCompiler(
[
createRenderProtoView()
],
[rootProtoView, mainProtoView]
[[rootProtoView], [mainProtoView]]
);
compiler.compileInHost(MainComponent).then( (protoViewRef) => {
expect(internalProtoView(protoViewRef)).toBe(rootProtoView);
@ -400,13 +399,13 @@ export function main() {
});
}
function createDirectiveBinding(reader, type) {
var meta = reader.read(type);
return DirectiveBinding.createFromType(meta.type, meta.annotation);
function createDirectiveBinding(directiveResolver, type) {
var annotation = directiveResolver.resolve(type);
return DirectiveBinding.createFromType(type, annotation);
}
function createProtoView(elementBinders = null) {
var pv = new AppProtoView(null, null, null, null, null);
var pv = new AppProtoView(null, null, MapWrapper.create());
if (isBlank(elementBinders)) {
elementBinders = [];
}
@ -414,8 +413,8 @@ function createProtoView(elementBinders = null) {
return pv;
}
function createComponentElementBinder(reader, type) {
var binding = createDirectiveBinding(reader, type);
function createComponentElementBinder(directiveResolver, type) {
var binding = createDirectiveBinding(directiveResolver, type);
return new ElementBinder(
0, null, 0,
null, binding
@ -431,12 +430,16 @@ function createViewportElementBinder(nestedProtoView) {
return elBinder;
}
function createRenderProtoView(elementBinders = null) {
function createRenderProtoView(elementBinders = null, type:number = null) {
if (isBlank(type)) {
type = renderApi.ProtoViewDto.COMPONENT_VIEW_TYPE;
}
if (isBlank(elementBinders)) {
elementBinders = [];
}
return new renderApi.ProtoViewDto({
elementBinders: elementBinders
elementBinders: elementBinders,
type: type
});
}
@ -549,8 +552,8 @@ class FakeProtoViewFactory extends ProtoViewFactory {
this._results = results;
}
createProtoView(parentProtoView, componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto,
directives:List<DirectiveBinding>):AppProtoView {
createAppProtoViews(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto,
directives:List<DirectiveBinding>):List<AppProtoView> {
ListWrapper.push(this.requests, [componentBinding, renderProtoView, directives]);
return ListWrapper.removeAt(this._results, 0);
}

View File

@ -1,57 +1,29 @@
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 {Directive, Component} from 'angular2/src/core/annotations_impl/annotations';
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
import {Injectable, Injector} from 'angular2/di';
@Injectable()
class SomeInjectable {}
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {Directive} from 'angular2/src/core/annotations_impl/annotations';
@Directive({selector: 'someDirective'})
class SomeDirective {}
@Component({selector: 'someComponent', injectables: [SomeInjectable]})
class SomeComponent {}
class SomeDirectiveWithoutAnnotation {
}
export function main() {
describe("DirectiveMetadataReader", () => {
describe("DirectiveResolver", () => {
var reader;
beforeEach(() => {
reader = new DirectiveMetadataReader();
reader = new DirectiveResolver();
});
it('should read out the Directive annotation', () => {
var directiveMetadata = reader.read(SomeDirective);
expect(directiveMetadata).toEqual(
new DirectiveMetadata(SomeDirective, new Directive({selector: 'someDirective'}), null));
});
it('should read out the Component annotation', () => {
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);
var directiveMetadata = reader.resolve(SomeDirective);
expect(directiveMetadata).toEqual(new Directive({selector: 'someDirective'}));
});
it('should throw if not matching annotation is found', () => {
expect(() => {
reader.read(SomeDirectiveWithoutAnnotation);
reader.resolve(SomeDirectiveWithoutAnnotation);
}).toThrowError('No Directive annotation found on SomeDirectiveWithoutAnnotation');
});
});

View File

@ -506,7 +506,7 @@ export function main() {
});
it("should instantiate directives that depend on pre built objects", function () {
var protoView = new AppProtoView(null, null, null, null, null);
var protoView = new AppProtoView(null, null, null);
var inj = injector([NeedsProtoViewRef], null, false, new PreBuiltObjects(null, null, protoView));
expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView));
@ -800,7 +800,7 @@ export function main() {
});
it("should inject ProtoViewRef", function () {
var protoView = new AppProtoView(null, null, null, null, null);
var protoView = new AppProtoView(null, null, null);
var inj = injector([NeedsProtoViewRef], null, false, new PreBuiltObjects(null, null, protoView));
expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView));

View File

@ -0,0 +1,109 @@
import {
AsyncTestCompleter,
beforeEach,
xdescribe,
ddescribe,
describe,
el,
expect,
iit,
inject,
IS_DARTIUM,
it,
SpyObject, proxy
} from 'angular2/test_lib';
import {isBlank} from 'angular2/src/facade/lang';
import {MapWrapper} from 'angular2/src/facade/collection';
import {ChangeDetection, ChangeDetectorDefinition} from 'angular2/change_detection';
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
import * as renderApi from 'angular2/src/render/api';
export function main() {
// TODO(tbosch): add missing tests
describe('ProtoViewFactory', () => {
var changeDetection;
var protoViewFactory;
var directiveResolver;
beforeEach( () => {
directiveResolver = new DirectiveResolver();
changeDetection = new ChangeDetectionSpy();
protoViewFactory = new ProtoViewFactory(changeDetection);
});
function bindDirective(type) {
return DirectiveBinding.createFromType(type, directiveResolver.resolve(type));
}
describe('getChangeDetectorDefinitions', () => {
it('should create a ChangeDetectorDefinition for the root render proto view', () => {
var renderPv = createRenderProtoView();
var defs = protoViewFactory.getChangeDetectorDefinitions(bindDirective(MainComponent).metadata,
renderPv, []);
expect(defs.length).toBe(1);
expect(defs[0].id).toEqual('MainComponent_comp_0');
});
});
describe('createAppProtoViews', () => {
it('should create an AppProtoView for the root render proto view', () => {
var renderPv = createRenderProtoView();
var pvs = protoViewFactory.createAppProtoViews(bindDirective(MainComponent),
renderPv, []);
expect(pvs.length).toBe(1);
expect(pvs[0].render).toBe(renderPv.render);
});
});
});
}
function createRenderProtoView(elementBinders = null, type:number = null) {
if (isBlank(type)) {
type = renderApi.ProtoViewDto.COMPONENT_VIEW_TYPE;
}
if (isBlank(elementBinders)) {
elementBinders = [];
}
return new renderApi.ProtoViewDto({
elementBinders: elementBinders,
type: type,
variableBindings: MapWrapper.create()
});
}
function createRenderComponentElementBinder(directiveIndex) {
return new renderApi.ElementBinder({
directives: [new renderApi.DirectiveBinder({
directiveIndex: directiveIndex
})]
});
}
function createRenderViewportElementBinder(nestedProtoView) {
return new renderApi.ElementBinder({
nestedProtoView: nestedProtoView
});
}
@proxy
@IMPLEMENTS(ChangeDetection)
class ChangeDetectionSpy extends SpyObject {
constructor(){super(ChangeDetection);}
noSuchMethod(m){return super.noSuchMethod(m)}
}
@Component({
selector: 'main-comp'
})
class MainComponent {}

View File

@ -37,7 +37,7 @@ export function main() {
}
function createProtoView() {
return new AppProtoView(null, null, null, null, null);
return new AppProtoView(null, null, null);
}
function createView() {

View File

@ -24,7 +24,7 @@ import {ElementRef} from 'angular2/src/core/compiler/element_ref';
import {Renderer, RenderViewRef, RenderProtoViewRef, RenderViewContainerRef} from 'angular2/src/render/api';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {DirectiveBinding, ElementInjector} from 'angular2/src/core/compiler/element_injector';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {Component} from 'angular2/src/core/annotations_impl/annotations';
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
@ -38,7 +38,7 @@ export function main() {
var utils;
var viewPool;
var manager;
var reader;
var directiveResolver;
var createdViews;
var createdRenderViews;
@ -55,8 +55,8 @@ export function main() {
}
function createDirectiveBinding(type) {
var meta = reader.read(type);
return DirectiveBinding.createFromType(meta.type, meta.annotation);
var annotation = directiveResolver.resolve(type);
return DirectiveBinding.createFromType(type, annotation);
}
function createEmptyElBinder() {
@ -80,7 +80,7 @@ export function main() {
staticChildComponentCount++;
}
}
var res = new AppProtoView(new MockProtoViewRef(staticChildComponentCount), null, null, null, null);
var res = new AppProtoView(new MockProtoViewRef(staticChildComponentCount), null, null);
res.elementBinders = binders;
return res;
}
@ -117,7 +117,7 @@ export function main() {
}
beforeEach( () => {
reader = new DirectiveMetadataReader();
directiveResolver = new DirectiveResolver();
renderer = new SpyRenderer();
utils = new SpyAppViewManagerUtils();
viewPool = new SpyAppViewPool();

View File

@ -24,7 +24,7 @@ import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
import {ChangeDetector} from 'angular2/change_detection';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {DirectiveBinding, ElementInjector, PreBuiltObjects} from 'angular2/src/core/compiler/element_injector';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {Component} from 'angular2/src/core/annotations_impl/annotations';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
@ -34,7 +34,7 @@ export function main() {
describe('AppViewManagerUtils', () => {
var metadataReader;
var directiveResolver;
var utils;
function createInjector() {
@ -42,8 +42,8 @@ export function main() {
}
function createDirectiveBinding(type) {
var meta = metadataReader.read(type);
return DirectiveBinding.createFromType(meta.type, meta.annotation);
var annotation = directiveResolver.resolve(type);
return DirectiveBinding.createFromType(type, annotation);
}
function createEmptyElBinder() {
@ -61,7 +61,7 @@ export function main() {
if (isBlank(binders)) {
binders = [];
}
var res = new AppProtoView(null, null, null, null, null);
var res = new AppProtoView(null, null, null);
res.elementBinders = binders;
return res;
}
@ -100,8 +100,8 @@ export function main() {
}
beforeEach( () => {
metadataReader = new DirectiveMetadataReader();
utils = new AppViewManagerUtils(metadataReader);
directiveResolver = new DirectiveResolver();
utils = new AppViewManagerUtils(directiveResolver);
});
describe('hydrateDynamicComponentInElementInjector', () => {

View File

@ -26,7 +26,7 @@ export function main() {
}
function createProtoView() {
return new AppProtoView(null, null, null, null, null);
return new AppProtoView(null, null, null);
}
function createView(pv) {