fix(ivy): blueprints should be synced whenever they are off (#27281)

PR Close #27281
This commit is contained in:
Kara Erickson 2018-11-26 15:27:59 -08:00 committed by Jason Aden
parent a7ba05ad82
commit d0e8020506
4 changed files with 77 additions and 22 deletions

View File

@ -62,7 +62,6 @@ const enum BindingDirection {
*/ */
export function refreshDescendantViews(viewData: LViewData, rf: RenderFlags | null) { export function refreshDescendantViews(viewData: LViewData, rf: RenderFlags | null) {
const tView = getTView(); const tView = getTView();
const parentFirstTemplatePass = getFirstTemplatePass();
// This needs to be set before children are processed to support recursive components // This needs to be set before children are processed to support recursive components
tView.firstTemplatePass = false; tView.firstTemplatePass = false;
@ -91,7 +90,7 @@ export function refreshDescendantViews(viewData: LViewData, rf: RenderFlags | nu
setHostBindings(tView, viewData); 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. */ /** Refreshes child components in the current view. */
function refreshChildComponents( function refreshChildComponents(components: number[] | null, rf: RenderFlags | null): void {
components: number[] | null, parentFirstTemplatePass: boolean, rf: RenderFlags | null): void {
if (components != null) { if (components != null) {
for (let i = 0; i < components.length; i++) { 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. * Refreshes components by entering the component view and processing its bindings, queries, etc.
* *
* @param adjustedElementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET) * @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<T>( export function componentRefresh<T>(adjustedElementIndex: number, rf: RenderFlags | null): void {
adjustedElementIndex: number, parentFirstTemplatePass: boolean, rf: RenderFlags | null): void {
ngDevMode && assertDataInRange(adjustedElementIndex); ngDevMode && assertDataInRange(adjustedElementIndex);
const hostView = getComponentViewByIndex(adjustedElementIndex, getViewData()); const hostView = getComponentViewByIndex(adjustedElementIndex, getViewData());
ngDevMode && assertNodeType(getTView().data[adjustedElementIndex] as TNode, TNodeType.Element); ngDevMode && assertNodeType(getTView().data[adjustedElementIndex] as TNode, TNodeType.Element);
// Only attached CheckAlways components or attached, dirty OnPush components should be checked // Only attached CheckAlways components or attached, dirty OnPush components should be checked
if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) { if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
parentFirstTemplatePass && syncViewWithBlueprint(hostView); syncViewWithBlueprint(hostView);
detectChangesInternal(hostView, hostView[CONTEXT], rf); detectChangesInternal(hostView, hostView[CONTEXT], rf);
} }
} }

View File

@ -230,9 +230,6 @@
{ {
"name": "extractPipeDef" "name": "extractPipeDef"
}, },
{
"name": "firstTemplatePass"
},
{ {
"name": "generateExpandoInstructionBlock" "name": "generateExpandoInstructionBlock"
}, },
@ -263,9 +260,6 @@
{ {
"name": "getFirstParentNative" "name": "getFirstParentNative"
}, },
{
"name": "getFirstTemplatePass"
},
{ {
"name": "getHighestElementContainer" "name": "getHighestElementContainer"
}, },

View File

@ -782,9 +782,6 @@
{ {
"name": "findViaComponent" "name": "findViaComponent"
}, },
{
"name": "firstTemplatePass"
},
{ {
"name": "flattenUnsubscriptionErrors" "name": "flattenUnsubscriptionErrors"
}, },
@ -860,9 +857,6 @@
{ {
"name": "getFirstParentNative" "name": "getFirstParentNative"
}, },
{
"name": "getFirstTemplatePass"
},
{ {
"name": "getHighestElementContainer" "name": "getHighestElementContainer"
}, },

View File

@ -8,13 +8,13 @@
import {NgForOfContext} from '@angular/common'; 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 {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 {RenderFlags} from '../../src/render3/interfaces/definition';
import {getCurrentView, restoreView} from '../../src/render3/state'; import {getCurrentView, restoreView} from '../../src/render3/state';
import {NgForOf, NgIf, NgTemplateOutlet} from './common_with_def'; import {NgForOf, NgIf, NgTemplateOutlet} from './common_with_def';
import {ComponentFixture} from './render_util'; import {ComponentFixture, createDirective, getDirectiveOnNode} from './render_util';
describe('@angular/common integration', () => { describe('@angular/common integration', () => {
@ -131,6 +131,75 @@ describe('@angular/common integration', () => {
.toEqual('<ul><li>0 of 3: first</li><li>1 of 3: middle</li><li>2 of 3: second</li></ul>'); .toEqual('<ul><li>0 of 3: first</li><li>1 of 3: middle</li><li>2 of 3: second</li></ul>');
}); });
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<string>) {
if (rf & RenderFlags.Create) {
element(0, 'comp');
}
}
/** <comp *ngFor="let row of rows"></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><div dir="">comp text</div></comp><comp><div dir="">comp text</div></comp>');
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><div dir="">comp text</div></comp><comp><div dir="">comp text</div></comp><comp><div dir="">comp text</div></comp>');
});
it('should retain parent view listeners when the NgFor destroy views', () => { it('should retain parent view listeners when the NgFor destroy views', () => {