fix(compiler): changed the compiler to set up event listeners and host properties on host view elements

Closes #1584
This commit is contained in:
vsavkin 2015-04-27 15:14:30 -07:00 committed by Misko Hevery
parent 414e58edb5
commit e3c11045bf
14 changed files with 187 additions and 93 deletions

View File

@ -90,7 +90,9 @@ export class Compiler {
compileInHost(componentTypeOrBinding:any):Promise<AppProtoView> {
var componentBinding = this._bindDirective(componentTypeOrBinding);
this._assertTypeIsComponent(componentBinding);
return this._renderer.createHostProtoView('host').then( (hostRenderPv) => {
var directiveMetadata = Compiler.buildRenderDirective(componentBinding);
return this._renderer.createHostProtoView(directiveMetadata).then( (hostRenderPv) => {
return this._compileNestedProtoViews(null, hostRenderPv, [componentBinding], true);
});
}

View File

@ -2,8 +2,9 @@ import {Injectable} from 'angular2/di';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {BaseException} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewDefinition, ProtoViewDto} from '../../api';
import {ViewDefinition, ProtoViewDto, DirectiveMetadata} from '../../api';
import {CompilePipeline} from './compile_pipeline';
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
@ -32,12 +33,21 @@ export class Compiler {
);
}
_compileTemplate(template: ViewDefinition, tplElement):Promise<ProtoViewDto> {
var subTaskPromises = [];
var pipeline = new CompilePipeline(this._stepFactory.createSteps(template, subTaskPromises));
var compileElements;
compileHost(directiveMetadata: DirectiveMetadata):Promise<ProtoViewDto> {
var hostViewDef = new ViewDefinition({
componentId: directiveMetadata.id,
absUrl: null,
template: null,
directives: [directiveMetadata]
});
var element = DOM.createElement(directiveMetadata.selector);
return this._compileTemplate(hostViewDef, element);
}
compileElements = pipeline.process(tplElement, template.componentId);
_compileTemplate(viewDef: ViewDefinition, tplElement):Promise<ProtoViewDto> {
var subTaskPromises = [];
var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef, subTaskPromises));
var compileElements = pipeline.process(tplElement, viewDef.componentId);
var protoView = compileElements[0].inheritedProtoView.build();

View File

@ -27,11 +27,20 @@ export class DirectiveParser extends CompileStep {
this._selectorMatcher = new SelectorMatcher();
this._directives = directives;
for (var i=0; i<directives.length; i++) {
var selector = CssSelector.parse(directives[i].selector);
var directive = directives[i];
var selector = CssSelector.parse(directive.selector);
this._ensureComponentOnlyHasElementSelector(selector, directive);
this._selectorMatcher.addSelectables(selector, i);
}
}
_ensureComponentOnlyHasElementSelector(selector, directive) {
var isElementSelector = selector.length === 1 && selector[0].isElementSelector();
if (! isElementSelector && directive.type === DirectiveMetadata.COMPONENT_TYPE) {
throw new BaseException(`Component '${directive.id}' can only have an element selector, but had '${directive.selector}'`);
}
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var attrs = current.attrs();
var classList = current.classList();

View File

@ -69,6 +69,13 @@ export class CssSelector {
this.notSelector = null;
}
isElementSelector():boolean {
return isPresent(this.element) &&
ListWrapper.isEmpty(this.classNames) &&
ListWrapper.isEmpty(this.attrs) &&
isBlank(this.notSelector);
}
setElement(element:string = null) {
if (isPresent(element)) {
element = element.toLowerCase();

View File

@ -11,7 +11,6 @@ import {RenderViewHydrator} from './view/view_hydrator';
import {Compiler} from './compiler/compiler';
import {ShadowDomStrategy} from './shadow_dom/shadow_dom_strategy';
import {ProtoViewBuilder} from './view/proto_view_builder';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewContainer} from './view/view_container';
function _resolveViewContainer(vc:api.RenderViewContainerRef) {
@ -80,15 +79,8 @@ export class DirectDomRenderer extends api.Renderer {
this._shadowDomStrategy = shadowDomStrategy;
}
createHostProtoView(componentId):Promise<api.ProtoViewDto> {
var rootElement = DOM.createElement('div');
var hostProtoViewBuilder = new ProtoViewBuilder(rootElement);
var elBinder = hostProtoViewBuilder.bindElement(rootElement, 'root element');
elBinder.setComponentId(componentId);
elBinder.bindDirective(0);
this._shadowDomStrategy.processElement(null, componentId, rootElement);
return PromiseWrapper.resolve(hostProtoViewBuilder.build());
createHostProtoView(directiveMetadata:api.DirectiveMetadata):Promise<api.ProtoViewDto> {
return this._compiler.compileHost(directiveMetadata);
}
createImperativeComponentProtoView(rendererId):Promise<api.ProtoViewDto> {
@ -97,10 +89,10 @@ export class DirectDomRenderer extends api.Renderer {
return PromiseWrapper.resolve(protoViewBuilder.build());
}
compile(template:api.ViewDefinition):Promise<api.ProtoViewDto> {
compile(view:api.ViewDefinition):Promise<api.ProtoViewDto> {
// Note: compiler already uses a DirectDomProtoViewRef, so we don't
// need to do anything here
return this._compiler.compile(template);
return this._compiler.compile(view);
}
mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List<api.ProtoViewRef>) {

View File

@ -22,6 +22,8 @@ import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_compone
import {ElementRef} from 'angular2/src/core/compiler/element_injector';
import {If} from 'angular2/src/directives/if';
import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer';
import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() {
describe('DynamicComponentLoader', function () {
@ -134,6 +136,29 @@ export function main() {
});
});
}));
it('should update host properties', inject([DynamicComponentLoader, TestBed, AsyncTestCompleter],
(loader, tb, async) => {
tb.overrideView(MyComp, new View({
template: '<div><location #loc></location></div>',
directives: [Location]
}));
tb.createView(MyComp).then((view) => {
var location = view.rawView.locals.get("loc");
loader.loadNextToExistingLocation(DynamicallyLoadedWithHostProps, location.elementRef).then(ref => {
ref.instance.id = "new value";
view.detectChanges();
var newlyInsertedElement = DOM.childNodesAsList(view.rootNodes[0])[1];
expect(newlyInsertedElement.id).toEqual("new value")
async.done();
});
});
}));
});
describe('loading into a new location', () => {
@ -242,6 +267,19 @@ class DynamicallyLoaded {
class DynamicallyLoaded2 {
}
@Component({
selector: 'dummy',
hostProperties: {'id' : 'id'}
})
@View({template: "DynamicallyLoadedWithHostProps;"})
class DynamicallyLoadedWithHostProps {
id:string;
constructor() {
this.id = "default";
}
}
@Component({
selector: 'location'
})
@ -254,7 +292,9 @@ class Location {
}
}
@Component()
@Component({
selector: 'my-comp'
})
@View({
directives: []
})

View File

@ -255,20 +255,19 @@ export function main() {
tb.overrideView(MyComp,
new View({
template: '<p [id]="ctxProp"></p>',
directives: [IdComponent]
directives: [IdDir]
}));
tb.createView(MyComp, {context: ctx}).then((view) => {
var idDir = view.rawView.elementInjectors[0].get(IdDir);
ctx.ctxProp = 'some_id';
view.detectChanges();
expect(view.rootNodes[0].id).toEqual('some_id');
expect(view.rootNodes).toHaveText('Matched on id with some_id');
expect(idDir.id).toEqual('some_id');
ctx.ctxProp = 'other_id';
view.detectChanges();
expect(view.rootNodes[0].id).toEqual('other_id');
expect(view.rootNodes).toHaveText('Matched on id with other_id');
expect(idDir.id).toEqual('other_id');
async.done();
});
@ -932,7 +931,9 @@ class PushCmpWithRef {
}
}
@Component()
@Component({
selector: 'my-comp'
})
@View({directives: [
]})
class MyComp {
@ -1192,14 +1193,11 @@ class DecoratorListeningDomEventNoPrevent {
}
}
@Component({
@Decorator({
selector: '[id]',
properties: {'id': 'id'}
})
@View({
template: '<div>Matched on id with {{id}}</div>'
})
class IdComponent {
class IdDir {
id: string;
}

View File

@ -1,4 +1,4 @@
/*
/*
* Runs compiler tests using in-browser DOM adapter.
*/

View File

@ -17,7 +17,7 @@ import {Type, isBlank, stringify, isPresent} from 'angular2/src/facade/lang';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {Compiler, CompilerCache} from 'angular2/src/render/dom/compiler/compiler';
import {ProtoViewDto, ViewDefinition} from 'angular2/src/render/api';
import {ProtoViewDto, ViewDefinition, DirectiveMetadata} from 'angular2/src/render/api';
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step'
import {CompileStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory';
@ -54,6 +54,22 @@ export function runCompilerCommonTests() {
});
}));
it('should run the steps and build the proto view', inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler((parent, current, control) => {
current.inheritedProtoView.bindVariable('b', 'a');
});
var dirMetadata = new DirectiveMetadata({id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE});
compiler.compileHost(dirMetadata).then( (protoView) => {
expect(DOM.tagName(protoView.render.delegate.element)).toEqual('CUSTOM')
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
expect(protoView.variableBindings).toEqual(MapWrapper.createFromStringMap({
'a': 'b'
}));
async.done();
});
}));
it('should use the inline template and compile in sync', inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler(EMPTY_STEP);
compiler.compile(new ViewDefinition({
@ -124,11 +140,14 @@ export function runCompilerCommonTests() {
class MockStepFactory extends CompileStepFactory {
steps:List<CompileStep>;
subTaskPromises:List<Promise>;
viewDef:ViewDefinition;
constructor(steps) {
super();
this.steps = steps;
}
createSteps(template, subTaskPromises) {
createSteps(viewDef, subTaskPromises) {
this.viewDef = viewDef;
this.subTaskPromises = subTaskPromises;
ListWrapper.forEach(this.subTaskPromises, (p) => ListWrapper.push(subTaskPromises, p) );
return this.steps;

View File

@ -1,5 +1,5 @@
import {describe, beforeEach, it, xit, expect, iit, ddescribe, el} from 'angular2/test_lib';
import {isPresent, assertionsEnabled} from 'angular2/src/facade/lang';
import {isPresent, isBlank, assertionsEnabled} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {DirectiveParser} from 'angular2/src/render/dom/compiler/directive_parser';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
@ -17,11 +17,11 @@ export function main() {
annotatedDirectives = [
someComponent,
someComponent2,
someComponent3,
someViewport,
someViewport2,
someDecorator,
someDecoratorIgnoringChildren,
decoratorWithMultipleAttrs,
someDecoratorWithProps,
someDecoratorWithHostProperties,
someDecoratorWithEvents,
@ -30,7 +30,9 @@ export function main() {
parser = new Parser(new Lexer());
});
function createPipeline(propertyBindings = null) {
function createPipeline(propertyBindings = null, directives = null) {
if (isBlank(directives)) directives = annotatedDirectives;
return new CompilePipeline([
new MockStep( (parent, current, control) => {
if (isPresent(propertyBindings)) {
@ -39,12 +41,12 @@ export function main() {
});
}
}),
new DirectiveParser(parser, annotatedDirectives)
new DirectiveParser(parser, directives)
]);
}
function process(el, propertyBindings = null) {
var pipeline = createPipeline(propertyBindings);
function process(el, propertyBindings = null, directives = null) {
var pipeline = createPipeline(propertyBindings, directives);
return ListWrapper.map(pipeline.process(el), (ce) => ce.inheritedElementBinder );
}
@ -63,7 +65,7 @@ export function main() {
it('should detect directives with multiple attributes', () => {
var results = process(el('<input type=text control=one></input>'));
expect(results[0].directives[0].directiveIndex).toBe(
annotatedDirectives.indexOf(someComponent3)
annotatedDirectives.indexOf(decoratorWithMultipleAttrs)
);
});
@ -176,30 +178,27 @@ export function main() {
describe('component directives', () => {
it('should save the component id', () => {
var results = process(
el('<div some-comp></div>')
el('<some-comp></some-comp>')
);
expect(results[0].componentId).toEqual('someComponent');
});
it('should throw when the provided selector is not an element selector', () => {
expect( () => {
createPipeline(null, [componentWithNonElementSelector]);
}).toThrowError(`Component 'componentWithNonElementSelector' can only have an element selector, but had '[attr]'`);
});
it('should not allow multiple component directives on the same element', () => {
expect( () => {
process(
el('<div some-comp some-comp2></div>')
el('<some-comp></some-comp>'),
null,
[someComponent, someComponentDup]
);
}).toThrowError('Only one component directive is allowed per element - check '
+ (assertionsEnabled() ? '<div some-comp some-comp2>' : 'null'));
}).toThrowError(new RegExp('Only one component directive is allowed per element' ));
});
it('should not allow component directives on <template> elements', () => {
expect( () => {
process(
el('<template some-comp></template>')
);
}).toThrowError('Only template directives are allowed on template elements - check '
+ (assertionsEnabled() ? '<template some-comp>' : 'null'));
});
});
});
}
@ -215,20 +214,20 @@ class MockStep extends CompileStep {
}
var someComponent = new DirectiveMetadata({
selector: '[some-comp]',
selector: 'some-comp',
id: 'someComponent',
type: DirectiveMetadata.COMPONENT_TYPE
});
var someComponent2 = new DirectiveMetadata({
selector: '[some-comp2]',
id: 'someComponent2',
var someComponentDup = new DirectiveMetadata({
selector: 'some-comp',
id: 'someComponentDup',
type: DirectiveMetadata.COMPONENT_TYPE
});
var someComponent3 = new DirectiveMetadata({
selector: 'input[type=text][control]',
id: 'someComponent3',
var someComponent2 = new DirectiveMetadata({
selector: 'some-comp2',
id: 'someComponent2',
type: DirectiveMetadata.COMPONENT_TYPE
});
@ -245,12 +244,21 @@ var someViewport2 = new DirectiveMetadata({
});
var someDecorator = new DirectiveMetadata({
selector: '[some-decor]'
selector: '[some-decor]',
type: DirectiveMetadata.DECORATOR_TYPE
});
var someDecoratorIgnoringChildren = new DirectiveMetadata({
selector: '[some-decor-ignoring-children]',
compileChildren: false
compileChildren: false,
type: DirectiveMetadata.DECORATOR_TYPE
});
var decoratorWithMultipleAttrs = new DirectiveMetadata({
selector: 'input[type=text][control]',
id: 'decoratorWithMultipleAttrs',
type: DirectiveMetadata.DECORATOR_TYPE
});
var someDecoratorWithProps = new DirectiveMetadata({
@ -282,3 +290,9 @@ var someDecoratorWithGlobalEvents = new DirectiveMetadata({
'window:resize': 'doItGlobal()'
})
});
var componentWithNonElementSelector = new DirectiveMetadata({
id: 'componentWithNonElementSelector',
selector: '[attr]',
type: DirectiveMetadata.COMPONENT_TYPE
});

View File

@ -41,7 +41,7 @@ export function main() {
it('should create host views while using the given elements in place', inject([AsyncTestCompleter], (async) => {
createRenderer();
renderer.createHostProtoView('someComponentId').then( (rootProtoView) => {
renderer.createHostProtoView(someComponent).then( (rootProtoView) => {
expect(rootProtoView.elementBinders[0].directives[0].directiveIndex).toBe(0);
var viewRefs = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render);
expect(viewRefs.length).toBe(1);
@ -62,7 +62,7 @@ export function main() {
it('should add a static component', inject([AsyncTestCompleter], (async) => {
createRenderer();
renderer.createHostProtoView('someComponentId').then( (rootProtoView) => {
renderer.createHostProtoView(someComponent).then( (rootProtoView) => {
var template = new ViewDefinition({
componentId: 'someComponent',
template: 'hello',
@ -79,7 +79,7 @@ export function main() {
it('should add a a dynamic component', inject([AsyncTestCompleter], (async) => {
createRenderer();
renderer.createHostProtoView('someComponentId').then( (rootProtoView) => {
renderer.createHostProtoView(someComponent).then( (rootProtoView) => {
var template = new ViewDefinition({
componentId: 'someComponent',
template: 'hello',
@ -102,7 +102,7 @@ export function main() {
directives: []
})]
});
compileRoot('someComponent').then( (rootProtoView) => {
compileRoot(someComponent).then( (rootProtoView) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render);
renderer.setText(viewRefs[1], 0, 'hello');
expect(rootEl).toHaveText('hello');
@ -118,7 +118,7 @@ export function main() {
directives: []
})]
});
compileRoot('someComponent').then( (rootProtoView) => {
compileRoot(someComponent).then( (rootProtoView) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render);
renderer.setElementProperty(viewRefs[1], 0, 'value', 'hello');
expect(DOM.childNodes(rootEl)[0].value).toEqual('hello');
@ -134,7 +134,7 @@ export function main() {
directives: []
})]
});
compileRoot('someComponent').then( (rootProtoView) => {
compileRoot(someComponent).then( (rootProtoView) => {
var viewRef = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render)[1];
var vcProtoViewRef = rootProtoView.elementBinders[0]
.nestedProtoView.elementBinders[0].nestedProtoView.render;
@ -162,7 +162,7 @@ export function main() {
})],
viewCacheCapacity: 2
});
compileRoot('someComponent').then( (rootProtoView) => {
compileRoot(someComponent).then( (rootProtoView) => {
var viewRef = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render)[1];
var vcProtoViewRef = rootProtoView.elementBinders[0]
.nestedProtoView.elementBinders[0].nestedProtoView.render;
@ -189,7 +189,7 @@ export function main() {
directives: []
})]
});
compileRoot('someComponent').then( (rootProtoView) => {
compileRoot(someComponent).then( (rootProtoView) => {
var viewRef = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render)[1];
var dispatcher = new LoggingEventDispatcher();
renderer.setEventDispatcher(viewRef, dispatcher);

View File

@ -50,14 +50,9 @@ export class IntegrationTestbed {
this.renderer = new DirectDomRenderer(compiler, viewFactory, viewHydrator, shadowDomStrategy);
}
compileRoot(componentId):Promise<ProtoViewDto> {
return this.renderer.createHostProtoView(componentId).then( (rootProtoView) => {
return this._compileNestedProtoViews(rootProtoView, [
new DirectiveMetadata({
type: DirectiveMetadata.COMPONENT_TYPE,
id: componentId
})
]);
compileRoot(componentMetadata):Promise<ProtoViewDto> {
return this.renderer.createHostProtoView(componentMetadata).then( (rootProtoView) => {
return this._compileNestedProtoViews(rootProtoView, [componentMetadata]);
});
}

View File

@ -79,7 +79,7 @@ export function main() {
directives: [simple]
})]
});
compileRoot('main').then( (pv) => {
compileRoot(mainDir).then( (pv) => {
renderer.createInPlaceHostView(null, rootEl, pv.render);
expect(rootEl).toHaveText('SIMPLE(A)');
@ -98,7 +98,7 @@ export function main() {
directives: [empty]
})]
});
compileRoot('main').then( (pv) => {
compileRoot(mainDir).then( (pv) => {
renderer.createInPlaceHostView(null, rootEl, pv.render);
expect(rootEl).toHaveText('');
@ -117,7 +117,7 @@ export function main() {
directives: [dynamicComponent]
})]
});
compileRoot('main').then( (rootPv) => {
compileRoot(mainDir).then( (rootPv) => {
compile('simple').then( (simplePv) => {
var views = renderer.createInPlaceHostView(null, rootEl, rootPv.render);
renderer.createDynamicComponentView(views[1], 0, simplePv.render);
@ -141,7 +141,7 @@ export function main() {
directives: [multipleContentTagsComponent]
})]
});
compileRoot('main').then( (pv) => {
compileRoot(mainDir).then( (pv) => {
renderer.createInPlaceHostView(null, rootEl, pv.render);
expect(rootEl).toHaveText('(A, BC)');
@ -161,7 +161,7 @@ export function main() {
directives: [multipleContentTagsComponent]
})]
});
compileRoot('main').then( (pv) => {
compileRoot(mainDir).then( (pv) => {
renderer.createInPlaceHostView(null, rootEl, pv.render);
expect(rootEl).toHaveText('(, BAC)');
@ -181,7 +181,7 @@ export function main() {
directives: [multipleContentTagsComponent, manualViewportDirective]
})]
});
compileRoot('main').then( (pv) => {
compileRoot(mainDir).then( (pv) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render);
var vcRef = new RenderViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
@ -211,7 +211,7 @@ export function main() {
directives: [multipleContentTagsComponent, manualViewportDirective]
})]
});
compileRoot('main').then( (pv) => {
compileRoot(mainDir).then( (pv) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render);
var vcRef = new RenderViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
@ -241,7 +241,7 @@ export function main() {
directives: [outerWithIndirectNestedComponent]
})]
});
compileRoot('main').then( (pv) => {
compileRoot(mainDir).then( (pv) => {
renderer.createInPlaceHostView(null, rootEl, pv.render);
expect(rootEl).toHaveText('OUTER(SIMPLE(AB))');
@ -262,7 +262,7 @@ export function main() {
directives: [outerComponent, manualViewportDirective]
})]
});
compileRoot('main').then( (pv) => {
compileRoot(mainDir).then( (pv) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render);
var vcRef = new RenderViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
@ -288,7 +288,7 @@ export function main() {
directives: [conditionalContentComponent]
})]
});
compileRoot('main').then( (pv) => {
compileRoot(mainDir).then( (pv) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render);
var vcRef = new RenderViewContainerRef(viewRefs[2], 0);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
@ -321,7 +321,7 @@ export function main() {
})],
viewCacheCapacity: 5
});
compileRoot('main').then( (pv) => {
compileRoot(mainDir).then( (pv) => {
var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render);
var vcRef0 = new RenderViewContainerRef(viewRefs[2], 0);
var vcRef1 = new RenderViewContainerRef(viewRefs[3], 0);
@ -372,6 +372,12 @@ export function main() {
}
var mainDir = new DirectiveMetadata({
selector: 'main',
id: 'main',
type: DirectiveMetadata.COMPONENT_TYPE
});
var simple = new DirectiveMetadata({
selector: 'simple',
id: 'simple',

View File

@ -150,7 +150,9 @@ class ParentCmp {
constructor() {}
}
@Component()
@Component({
selector: 'my-comp'
})
class MyComp {
name;
}