Skip to content

Commit e3c1104

Browse files
vsavkinmhevery
authored andcommitted
fix(compiler): changed the compiler to set up event listeners and host properties on host view elements
Closes #1584
1 parent 414e58e commit e3c1104

14 files changed

+187
-93
lines changed

modules/angular2/src/core/compiler/compiler.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ export class Compiler {
9090
compileInHost(componentTypeOrBinding:any):Promise<AppProtoView> {
9191
var componentBinding = this._bindDirective(componentTypeOrBinding);
9292
this._assertTypeIsComponent(componentBinding);
93-
return this._renderer.createHostProtoView('host').then( (hostRenderPv) => {
93+
94+
var directiveMetadata = Compiler.buildRenderDirective(componentBinding);
95+
return this._renderer.createHostProtoView(directiveMetadata).then( (hostRenderPv) => {
9496
return this._compileNestedProtoViews(null, hostRenderPv, [componentBinding], true);
9597
});
9698
}

modules/angular2/src/render/dom/compiler/compiler.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import {Injectable} from 'angular2/di';
22

33
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
44
import {BaseException} from 'angular2/src/facade/lang';
5+
import {DOM} from 'angular2/src/dom/dom_adapter';
56

6-
import {ViewDefinition, ProtoViewDto} from '../../api';
7+
import {ViewDefinition, ProtoViewDto, DirectiveMetadata} from '../../api';
78
import {CompilePipeline} from './compile_pipeline';
89
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
910
import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
@@ -32,12 +33,21 @@ export class Compiler {
3233
);
3334
}
3435

35-
_compileTemplate(template: ViewDefinition, tplElement):Promise<ProtoViewDto> {
36-
var subTaskPromises = [];
37-
var pipeline = new CompilePipeline(this._stepFactory.createSteps(template, subTaskPromises));
38-
var compileElements;
36+
compileHost(directiveMetadata: DirectiveMetadata):Promise<ProtoViewDto> {
37+
var hostViewDef = new ViewDefinition({
38+
componentId: directiveMetadata.id,
39+
absUrl: null,
40+
template: null,
41+
directives: [directiveMetadata]
42+
});
43+
var element = DOM.createElement(directiveMetadata.selector);
44+
return this._compileTemplate(hostViewDef, element);
45+
}
3946

40-
compileElements = pipeline.process(tplElement, template.componentId);
47+
_compileTemplate(viewDef: ViewDefinition, tplElement):Promise<ProtoViewDto> {
48+
var subTaskPromises = [];
49+
var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef, subTaskPromises));
50+
var compileElements = pipeline.process(tplElement, viewDef.componentId);
4151

4252
var protoView = compileElements[0].inheritedProtoView.build();
4353

modules/angular2/src/render/dom/compiler/directive_parser.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,20 @@ export class DirectiveParser extends CompileStep {
2727
this._selectorMatcher = new SelectorMatcher();
2828
this._directives = directives;
2929
for (var i=0; i<directives.length; i++) {
30-
var selector = CssSelector.parse(directives[i].selector);
30+
var directive = directives[i];
31+
var selector = CssSelector.parse(directive.selector);
32+
this._ensureComponentOnlyHasElementSelector(selector, directive);
3133
this._selectorMatcher.addSelectables(selector, i);
3234
}
3335
}
3436

37+
_ensureComponentOnlyHasElementSelector(selector, directive) {
38+
var isElementSelector = selector.length === 1 && selector[0].isElementSelector();
39+
if (! isElementSelector && directive.type === DirectiveMetadata.COMPONENT_TYPE) {
40+
throw new BaseException(`Component '${directive.id}' can only have an element selector, but had '${directive.selector}'`);
41+
}
42+
}
43+
3544
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
3645
var attrs = current.attrs();
3746
var classList = current.classList();

modules/angular2/src/render/dom/compiler/selector.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ export class CssSelector {
6969
this.notSelector = null;
7070
}
7171

72+
isElementSelector():boolean {
73+
return isPresent(this.element) &&
74+
ListWrapper.isEmpty(this.classNames) &&
75+
ListWrapper.isEmpty(this.attrs) &&
76+
isBlank(this.notSelector);
77+
}
78+
7279
setElement(element:string = null) {
7380
if (isPresent(element)) {
7481
element = element.toLowerCase();

modules/angular2/src/render/dom/direct_dom_renderer.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {RenderViewHydrator} from './view/view_hydrator';
1111
import {Compiler} from './compiler/compiler';
1212
import {ShadowDomStrategy} from './shadow_dom/shadow_dom_strategy';
1313
import {ProtoViewBuilder} from './view/proto_view_builder';
14-
import {DOM} from 'angular2/src/dom/dom_adapter';
1514
import {ViewContainer} from './view/view_container';
1615

1716
function _resolveViewContainer(vc:api.RenderViewContainerRef) {
@@ -80,15 +79,8 @@ export class DirectDomRenderer extends api.Renderer {
8079
this._shadowDomStrategy = shadowDomStrategy;
8180
}
8281

83-
createHostProtoView(componentId):Promise<api.ProtoViewDto> {
84-
var rootElement = DOM.createElement('div');
85-
var hostProtoViewBuilder = new ProtoViewBuilder(rootElement);
86-
var elBinder = hostProtoViewBuilder.bindElement(rootElement, 'root element');
87-
elBinder.setComponentId(componentId);
88-
elBinder.bindDirective(0);
89-
90-
this._shadowDomStrategy.processElement(null, componentId, rootElement);
91-
return PromiseWrapper.resolve(hostProtoViewBuilder.build());
82+
createHostProtoView(directiveMetadata:api.DirectiveMetadata):Promise<api.ProtoViewDto> {
83+
return this._compiler.compileHost(directiveMetadata);
9284
}
9385

9486
createImperativeComponentProtoView(rendererId):Promise<api.ProtoViewDto> {
@@ -97,10 +89,10 @@ export class DirectDomRenderer extends api.Renderer {
9789
return PromiseWrapper.resolve(protoViewBuilder.build());
9890
}
9991

100-
compile(template:api.ViewDefinition):Promise<api.ProtoViewDto> {
92+
compile(view:api.ViewDefinition):Promise<api.ProtoViewDto> {
10193
// Note: compiler already uses a DirectDomProtoViewRef, so we don't
10294
// need to do anything here
103-
return this._compiler.compile(template);
95+
return this._compiler.compile(view);
10496
}
10597

10698
mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List<api.ProtoViewRef>) {

modules/angular2/test/core/compiler/dynamic_component_loader_spec.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_compone
2222
import {ElementRef} from 'angular2/src/core/compiler/element_injector';
2323
import {If} from 'angular2/src/directives/if';
2424
import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer';
25+
import {DOM} from 'angular2/src/dom/dom_adapter';
26+
2527

2628
export function main() {
2729
describe('DynamicComponentLoader', function () {
@@ -134,6 +136,29 @@ export function main() {
134136
});
135137
});
136138
}));
139+
140+
it('should update host properties', inject([DynamicComponentLoader, TestBed, AsyncTestCompleter],
141+
(loader, tb, async) => {
142+
tb.overrideView(MyComp, new View({
143+
template: '<div><location #loc></location></div>',
144+
directives: [Location]
145+
}));
146+
147+
tb.createView(MyComp).then((view) => {
148+
var location = view.rawView.locals.get("loc");
149+
150+
loader.loadNextToExistingLocation(DynamicallyLoadedWithHostProps, location.elementRef).then(ref => {
151+
ref.instance.id = "new value";
152+
153+
view.detectChanges();
154+
155+
var newlyInsertedElement = DOM.childNodesAsList(view.rootNodes[0])[1];
156+
expect(newlyInsertedElement.id).toEqual("new value")
157+
158+
async.done();
159+
});
160+
});
161+
}));
137162
});
138163

139164
describe('loading into a new location', () => {
@@ -242,6 +267,19 @@ class DynamicallyLoaded {
242267
class DynamicallyLoaded2 {
243268
}
244269

270+
@Component({
271+
selector: 'dummy',
272+
hostProperties: {'id' : 'id'}
273+
})
274+
@View({template: "DynamicallyLoadedWithHostProps;"})
275+
class DynamicallyLoadedWithHostProps {
276+
id:string;
277+
278+
constructor() {
279+
this.id = "default";
280+
}
281+
}
282+
245283
@Component({
246284
selector: 'location'
247285
})
@@ -254,7 +292,9 @@ class Location {
254292
}
255293
}
256294

257-
@Component()
295+
@Component({
296+
selector: 'my-comp'
297+
})
258298
@View({
259299
directives: []
260300
})

modules/angular2/test/core/compiler/integration_spec.js

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -255,20 +255,19 @@ export function main() {
255255
tb.overrideView(MyComp,
256256
new View({
257257
template: '<p [id]="ctxProp"></p>',
258-
directives: [IdComponent]
258+
directives: [IdDir]
259259
}));
260260

261261
tb.createView(MyComp, {context: ctx}).then((view) => {
262+
var idDir = view.rawView.elementInjectors[0].get(IdDir);
262263

263264
ctx.ctxProp = 'some_id';
264265
view.detectChanges();
265-
expect(view.rootNodes[0].id).toEqual('some_id');
266-
expect(view.rootNodes).toHaveText('Matched on id with some_id');
266+
expect(idDir.id).toEqual('some_id');
267267

268268
ctx.ctxProp = 'other_id';
269269
view.detectChanges();
270-
expect(view.rootNodes[0].id).toEqual('other_id');
271-
expect(view.rootNodes).toHaveText('Matched on id with other_id');
270+
expect(idDir.id).toEqual('other_id');
272271

273272
async.done();
274273
});
@@ -932,7 +931,9 @@ class PushCmpWithRef {
932931
}
933932
}
934933

935-
@Component()
934+
@Component({
935+
selector: 'my-comp'
936+
})
936937
@View({directives: [
937938
]})
938939
class MyComp {
@@ -1192,14 +1193,11 @@ class DecoratorListeningDomEventNoPrevent {
11921193
}
11931194
}
11941195

1195-
@Component({
1196+
@Decorator({
11961197
selector: '[id]',
11971198
properties: {'id': 'id'}
11981199
})
1199-
@View({
1200-
template: '<div>Matched on id with {{id}}</div>'
1201-
})
1202-
class IdComponent {
1200+
class IdDir {
12031201
id: string;
12041202
}
12051203

modules/angular2/test/render/dom/compiler/compiler_browser_spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
* Runs compiler tests using in-browser DOM adapter.
33
*/
44

modules/angular2/test/render/dom/compiler/compiler_common_tests.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {Type, isBlank, stringify, isPresent} from 'angular2/src/facade/lang';
1717
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
1818

1919
import {Compiler, CompilerCache} from 'angular2/src/render/dom/compiler/compiler';
20-
import {ProtoViewDto, ViewDefinition} from 'angular2/src/render/api';
20+
import {ProtoViewDto, ViewDefinition, DirectiveMetadata} from 'angular2/src/render/api';
2121
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
2222
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step'
2323
import {CompileStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory';
@@ -54,6 +54,22 @@ export function runCompilerCommonTests() {
5454
});
5555
}));
5656

57+
it('should run the steps and build the proto view', inject([AsyncTestCompleter], (async) => {
58+
var compiler = createCompiler((parent, current, control) => {
59+
current.inheritedProtoView.bindVariable('b', 'a');
60+
});
61+
62+
var dirMetadata = new DirectiveMetadata({id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE});
63+
compiler.compileHost(dirMetadata).then( (protoView) => {
64+
expect(DOM.tagName(protoView.render.delegate.element)).toEqual('CUSTOM')
65+
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
66+
expect(protoView.variableBindings).toEqual(MapWrapper.createFromStringMap({
67+
'a': 'b'
68+
}));
69+
async.done();
70+
});
71+
}));
72+
5773
it('should use the inline template and compile in sync', inject([AsyncTestCompleter], (async) => {
5874
var compiler = createCompiler(EMPTY_STEP);
5975
compiler.compile(new ViewDefinition({
@@ -124,11 +140,14 @@ export function runCompilerCommonTests() {
124140
class MockStepFactory extends CompileStepFactory {
125141
steps:List<CompileStep>;
126142
subTaskPromises:List<Promise>;
143+
viewDef:ViewDefinition;
144+
127145
constructor(steps) {
128146
super();
129147
this.steps = steps;
130148
}
131-
createSteps(template, subTaskPromises) {
149+
createSteps(viewDef, subTaskPromises) {
150+
this.viewDef = viewDef;
132151
this.subTaskPromises = subTaskPromises;
133152
ListWrapper.forEach(this.subTaskPromises, (p) => ListWrapper.push(subTaskPromises, p) );
134153
return this.steps;

0 commit comments

Comments
 (0)