fix(ivy): make ViewRef.detectChanges work with embedded views (FW-749) (#27521)

In `ViewRef.detectChanges`, we are passing `ViewRef.context` into `detectChanges` to trigger change detection. This only makes sense for component `ViewRefs` (i.e. injected `ChangeDetectorRefs`) because with embedded views, `context` is not a component instance where the view has been monkey-patched. It's a just a normal object, so the view will be undefined.

In order to resolve this problem, we now invoke `detectChangesInternal` and also pass `LView` (to make sure we always have a view available).

PR Close #27521
This commit is contained in:
Andrew Kushnir 2018-12-06 14:19:49 -08:00 committed by Alex Rickabaugh
parent 16d26e5f38
commit 05cdfb90e9
3 changed files with 27 additions and 27 deletions

View File

@ -2517,7 +2517,7 @@ export function checkNoChangesInRootView(lView: LView): void {
}
/** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */
function detectChangesInternal<T>(hostView: LView, component: T, rf: RenderFlags | null) {
export function detectChangesInternal<T>(hostView: LView, component: T, rf: RenderFlags | null) {
const hostTView = hostView[TVIEW];
const oldView = enterView(hostView, hostView[HOST_NODE]);
const templateFn = hostTView.template !;

View File

@ -11,7 +11,7 @@ import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detec
import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref';
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref';
import {checkNoChanges, checkNoChangesInRootView, detectChanges, detectChangesInRootView, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
import {checkNoChanges, checkNoChangesInRootView, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn, viewAttached} from './instructions';
import {TNode, TNodeType, TViewNode} from './interfaces/node';
import {FLAGS, HOST, HOST_NODE, LView, LViewFlags, PARENT, RENDERER_FACTORY} from './interfaces/view';
import {destroyLView} from './node_manipulation';
@ -244,7 +244,7 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
if (rendererFactory.begin) {
rendererFactory.begin();
}
detectChanges(this.context);
detectChangesInternal(this._lView, this.context, null);
if (rendererFactory.end) {
rendererFactory.end();
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {TemplateRef, ViewContainerRef} from '@angular/core';
import {EmbeddedViewRef, TemplateRef, ViewContainerRef} from '@angular/core';
import {withBody} from '@angular/private/testing';
import {ChangeDetectionStrategy, ChangeDetectorRef, DoCheck, RendererType2} from '../../src/core';
@ -91,13 +91,22 @@ describe('change detection', () => {
it('should support detectChanges on components that have LContainers', () => {
let structuralComp !: StructuralComp;
function FooTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
text(0);
}
if (rf & RenderFlags.Update) {
textBinding(0, bind(ctx.value));
}
}
class StructuralComp {
tmp !: TemplateRef<any>;
value = 'one';
constructor(public vcr: ViewContainerRef) {}
create() { this.vcr.createEmbeddedView(this.tmp); }
create() { return this.vcr.createEmbeddedView(this.tmp, this); }
static ngComponentDef = defineComponent({
type: StructuralComp,
@ -107,32 +116,17 @@ describe('change detection', () => {
inputs: {tmp: 'tmp'},
consts: 1,
vars: 1,
template: (rf: RenderFlags, ctx: StructuralComp) => {
if (rf & RenderFlags.Create) {
text(0);
}
if (rf & RenderFlags.Update) {
textBinding(0, bind(ctx.value));
}
}
template: FooTemplate
});
}
function FooTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
text(0, 'Temp content');
}
}
/**
* <ng-template #foo>
* Temp content
* </ng-template>
* <ng-template #foo>{{ value }}</ng-template>
* <structural-comp [tmp]="foo"></structural-comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
template(0, FooTemplate, 1, 0, '', null, ['foo', ''], templateRefExtractor);
template(0, FooTemplate, 2, 1, '', null, ['foo', ''], templateRefExtractor);
element(2, 'structural-comp');
}
if (rf & RenderFlags.Update) {
@ -145,13 +139,19 @@ describe('change detection', () => {
fixture.update();
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>');
structuralComp.create();
const viewRef: EmbeddedViewRef<any> = structuralComp.create();
fixture.update();
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>Temp content');
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>one');
// check embedded view update
structuralComp.value = 'two';
detectChanges(structuralComp);
expect(fixture.html).toEqual('<structural-comp>two</structural-comp>Temp content');
viewRef.detectChanges();
expect(fixture.html).toEqual('<structural-comp>one</structural-comp>two');
// check root view update
structuralComp.value = 'three';
fixture.update();
expect(fixture.html).toEqual('<structural-comp>three</structural-comp>three');
});
});