From d0e8020506642b768668f22c0432d8d99e480d68 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Mon, 26 Nov 2018 15:27:59 -0800 Subject: [PATCH] fix(ivy): blueprints should be synced whenever they are off (#27281) PR Close #27281 --- packages/core/src/render3/instructions.ts | 14 ++-- .../hello_world/bundle.golden_symbols.json | 6 -- .../hello_world_r2/bundle.golden_symbols.json | 6 -- .../test/render3/common_integration_spec.ts | 73 ++++++++++++++++++- 4 files changed, 77 insertions(+), 22 deletions(-) diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 79d6688545..8a99db8f49 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -62,7 +62,6 @@ const enum BindingDirection { */ export function refreshDescendantViews(viewData: LViewData, rf: RenderFlags | null) { const tView = getTView(); - const parentFirstTemplatePass = getFirstTemplatePass(); // This needs to be set before children are processed to support recursive components tView.firstTemplatePass = false; @@ -91,7 +90,7 @@ export function refreshDescendantViews(viewData: LViewData, rf: RenderFlags | nu setHostBindings(tView, viewData); } - refreshChildComponents(tView.components, parentFirstTemplatePass, rf); + refreshChildComponents(tView.components, rf); } @@ -147,11 +146,10 @@ function refreshContentQueries(tView: TView): void { } /** Refreshes child components in the current view. */ -function refreshChildComponents( - components: number[] | null, parentFirstTemplatePass: boolean, rf: RenderFlags | null): void { +function refreshChildComponents(components: number[] | null, rf: RenderFlags | null): void { if (components != null) { for (let i = 0; i < components.length; i++) { - componentRefresh(components[i], parentFirstTemplatePass, rf); + componentRefresh(components[i], rf); } } } @@ -2068,16 +2066,16 @@ export function embeddedViewEnd(): void { * Refreshes components by entering the component view and processing its bindings, queries, etc. * * @param adjustedElementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET) + * @param rf The render flags that should be used to process this template */ -export function componentRefresh( - adjustedElementIndex: number, parentFirstTemplatePass: boolean, rf: RenderFlags | null): void { +export function componentRefresh(adjustedElementIndex: number, rf: RenderFlags | null): void { ngDevMode && assertDataInRange(adjustedElementIndex); const hostView = getComponentViewByIndex(adjustedElementIndex, getViewData()); ngDevMode && assertNodeType(getTView().data[adjustedElementIndex] as TNode, TNodeType.Element); // Only attached CheckAlways components or attached, dirty OnPush components should be checked if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) { - parentFirstTemplatePass && syncViewWithBlueprint(hostView); + syncViewWithBlueprint(hostView); detectChangesInternal(hostView, hostView[CONTEXT], rf); } } diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 57a5183818..de04084e40 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -230,9 +230,6 @@ { "name": "extractPipeDef" }, - { - "name": "firstTemplatePass" - }, { "name": "generateExpandoInstructionBlock" }, @@ -263,9 +260,6 @@ { "name": "getFirstParentNative" }, - { - "name": "getFirstTemplatePass" - }, { "name": "getHighestElementContainer" }, diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index e34ca82a65..5162284265 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -782,9 +782,6 @@ { "name": "findViaComponent" }, - { - "name": "firstTemplatePass" - }, { "name": "flattenUnsubscriptionErrors" }, @@ -860,9 +857,6 @@ { "name": "getFirstParentNative" }, - { - "name": "getFirstTemplatePass" - }, { "name": "getHighestElementContainer" }, diff --git a/packages/core/test/render3/common_integration_spec.ts b/packages/core/test/render3/common_integration_spec.ts index 177cb16fd4..788ae28d36 100644 --- a/packages/core/test/render3/common_integration_spec.ts +++ b/packages/core/test/render3/common_integration_spec.ts @@ -8,13 +8,13 @@ import {NgForOfContext} from '@angular/common'; -import {AttributeMarker, defineComponent, templateRefExtractor} from '../../src/render3/index'; +import {AttributeMarker, defineComponent, element, templateRefExtractor} from '../../src/render3/index'; import {bind, template, elementEnd, elementProperty, elementStart, interpolation1, interpolation2, interpolation3, interpolationV, listener, load, nextContext, text, textBinding, elementContainerStart, elementContainerEnd, reference} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {getCurrentView, restoreView} from '../../src/render3/state'; import {NgForOf, NgIf, NgTemplateOutlet} from './common_with_def'; -import {ComponentFixture} from './render_util'; +import {ComponentFixture, createDirective, getDirectiveOnNode} from './render_util'; describe('@angular/common integration', () => { @@ -131,6 +131,75 @@ describe('@angular/common integration', () => { .toEqual('
  • 0 of 3: first
  • 1 of 3: middle
  • 2 of 3: second
'); }); + it('should instantiate directives inside directives properly in an ngFor', () => { + let dirs: any[] = []; + + const Dir = createDirective('dir'); + + class Comp { + static ngComponentDef = defineComponent({ + type: Comp, + selectors: [['comp']], + factory: () => new Comp(), + consts: 2, + vars: 0, + template: (rf: RenderFlags, cmp: Comp) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', ['dir', '']); + { text(1, 'comp text'); } + elementEnd(); + // testing only + dirs.push(getDirectiveOnNode(0)); + } + }, + directives: [Dir] + }); + } + + function ngForTemplate(rf: RenderFlags, ctx: NgForOfContext) { + if (rf & RenderFlags.Create) { + element(0, 'comp'); + } + } + + /** */ + class MyApp { + rows: string[] = ['first', 'second']; + + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + consts: 1, + vars: 1, + template: (rf: RenderFlags, ctx: MyApp) => { + if (rf & RenderFlags.Create) { + template(0, ngForTemplate, 1, 0, undefined, ['ngForOf', '']); + } + if (rf & RenderFlags.Update) { + elementProperty(0, 'ngForOf', bind(ctx.rows)); + } + }, + directives: () => [NgForOf, Comp, Dir] + }); + } + + const fixture = new ComponentFixture(MyApp); + expect(fixture.html) + .toEqual( + '
comp text
comp text
'); + expect(dirs.length).toBe(2); + expect(dirs[0] instanceof Dir).toBe(true); + expect(dirs[1] instanceof Dir).toBe(true); + + fixture.component.rows.push('third'); + fixture.update(); + expect(dirs.length).toBe(3); + expect(dirs[2] instanceof Dir).toBe(true); + expect(fixture.html) + .toEqual( + '
comp text
comp text
comp text
'); + }); it('should retain parent view listeners when the NgFor destroy views', () => {