From 10734ac6073f89bec5bf7f89c52cc3ba1d2d7bed Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Mon, 18 Mar 2019 14:52:30 -0700 Subject: [PATCH] fix(ivy): Class selector directives execute properly on container elements (#29383) PR Close #29383 --- .../src/render3/instructions/instructions.ts | 33 ++++++++-- packages/core/test/acceptance/content_spec.ts | 60 ++++++++++++++++++- .../cyclic_import/bundle.golden_symbols.json | 3 + .../bundling/todo/bundle.golden_symbols.json | 3 + 4 files changed, 92 insertions(+), 7 deletions(-) diff --git a/packages/core/src/render3/instructions/instructions.ts b/packages/core/src/render3/instructions/instructions.ts index 4f64b431ff..e3c3e78934 100644 --- a/packages/core/src/render3/instructions/instructions.ts +++ b/packages/core/src/render3/instructions/instructions.ts @@ -547,6 +547,13 @@ export function elementContainerStart( const tNode = createNodeAtIndex(index, TNodeType.ElementContainer, native, tagName, attrs || null); + + if (attrs) { + // While ng-container doesn't necessarily support styling, we use the style context to identify + // and execute directives on the ng-container. + setNodeStylingTemplate(tView, tNode, attrs, 0); + } + appendChild(native, tNode, lView); createDirectivesAndLocals(tView, lView, localRefs); attachPatchData(native, lView); @@ -559,6 +566,25 @@ export function elementContainerStart( executeContentQueries(tView, tNode, lView); } +/** + * Appropriately sets `stylingTemplate` on a TNode + * + * Does not apply styles to DOM nodes + * + * @param tNode The node whose `stylingTemplate` to set + * @param attrs The attribute array source to set the attributes from + * @param attrsStartIndex Optional start index to start processing the `attrs` from + */ +function setNodeStylingTemplate( + tView: TView, tNode: TNode, attrs: TAttributes, attrsStartIndex: number) { + if (tView.firstTemplatePass && !tNode.stylingTemplate) { + const stylingAttrsStartIndex = attrsStylingIndexOf(attrs, attrsStartIndex); + if (stylingAttrsStartIndex >= 0) { + tNode.stylingTemplate = initializeStaticStylingContext(attrs, stylingAttrsStartIndex); + } + } +} + function executeContentQueries(tView: TView, tNode: TNode, lView: LView) { if (isContentQueryHost(tNode)) { const start = tNode.directiveStart; @@ -636,12 +662,7 @@ export function elementStart( // value is evaluated). When the template is allocated (when it turns into a context) // then the styling template is locked and cannot be further extended (it can only be // instantiated into a context per element) - if (tView.firstTemplatePass && !tNode.stylingTemplate) { - const stylingAttrsStartIndex = attrsStylingIndexOf(attrs, lastAttrIndex); - if (stylingAttrsStartIndex >= 0) { - tNode.stylingTemplate = initializeStaticStylingContext(attrs, stylingAttrsStartIndex); - } - } + setNodeStylingTemplate(tView, tNode, attrs, lastAttrIndex); if (tNode.stylingTemplate) { // the initial style/class values are rendered immediately after having been diff --git a/packages/core/test/acceptance/content_spec.ts b/packages/core/test/acceptance/content_spec.ts index 12898d4a71..63b6348431 100644 --- a/packages/core/test/acceptance/content_spec.ts +++ b/packages/core/test/acceptance/content_spec.ts @@ -166,5 +166,63 @@ describe('projection', () => { fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('inline()ng-template(onetwothree)'); }); + + describe('on containers', () => { + it('should work when matching attributes', () => { + let xDirectives = 0; + @Component({selector: 'selector-proj', template: ''}) + class SelectedNgContentComp { + } + + @Directive({selector: '[x]'}) + class XDirective { + constructor() { xDirectives++; } + } + + @Component({ + selector: 'main-selector', + template: + 'Hello world!' + }) + class SelectorMainComp { + } + + TestBed.configureTestingModule( + {declarations: [XDirective, SelectedNgContentComp, SelectorMainComp]}); + const fixture = TestBed.createComponent(SelectorMainComp); + + fixture.detectChanges(); + expect(fixture.nativeElement).toHaveText('Hello world!'); + expect(xDirectives).toEqual(1); + }); + + it('should work when matching classes', () => { + let xDirectives = 0; + @Component({selector: 'selector-proj', template: ''}) + class SelectedNgContentComp { + } + + @Directive({selector: '.x'}) + class XDirective { + constructor() { xDirectives++; } + } + + @Component({ + selector: 'main-selector', + template: + 'Hello world!' + }) + class SelectorMainComp { + } + + TestBed.configureTestingModule( + {declarations: [XDirective, SelectedNgContentComp, SelectorMainComp]}); + const fixture = TestBed.createComponent(SelectorMainComp); + + fixture.detectChanges(); + expect(fixture.nativeElement).toHaveText('Hello world!'); + expect(xDirectives).toEqual(1); + }); + }); }); -}); \ No newline at end of file +}); diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index aca1ed856b..2cb9a466ab 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -659,6 +659,9 @@ { "name": "setIsParent" }, + { + "name": "setNodeStylingTemplate" + }, { "name": "setPreviousOrParentTNode" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index b0925d8341..7272669301 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -1271,6 +1271,9 @@ { "name": "setIsParent" }, + { + "name": "setNodeStylingTemplate" + }, { "name": "setPlayerBuilder" },