fix(ivy): calling ChangeDetectorRef.detectChanges() from onChanges should not go infinite loop (#28239)
PR Close #28239
This commit is contained in:
parent
664ea50b46
commit
873750609f
|
@ -68,6 +68,9 @@ export function refreshDescendantViews(lView: LView) {
|
||||||
tView.firstTemplatePass = false;
|
tView.firstTemplatePass = false;
|
||||||
setFirstTemplatePass(false);
|
setFirstTemplatePass(false);
|
||||||
|
|
||||||
|
// Resetting the bindingIndex of the current LView as the next steps may trigger change detection.
|
||||||
|
lView[BINDING_INDEX] = tView.bindingStartIndex;
|
||||||
|
|
||||||
// If this is a creation pass, we should not call lifecycle hooks or evaluate bindings.
|
// If this is a creation pass, we should not call lifecycle hooks or evaluate bindings.
|
||||||
// This will be done in the update pass.
|
// This will be done in the update pass.
|
||||||
if (!isCreationMode(lView)) {
|
if (!isCreationMode(lView)) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {withBody} from '@angular/private/testing';
|
||||||
|
|
||||||
import {ChangeDetectionStrategy, ChangeDetectorRef, DoCheck, RendererType2} from '../../src/core';
|
import {ChangeDetectionStrategy, ChangeDetectorRef, DoCheck, RendererType2} from '../../src/core';
|
||||||
import {whenRendered} from '../../src/render3/component';
|
import {whenRendered} from '../../src/render3/component';
|
||||||
import {LifecycleHooksFeature, defineComponent, defineDirective, getRenderedText, templateRefExtractor} from '../../src/render3/index';
|
import {LifecycleHooksFeature, NgOnChangesFeature, defineComponent, defineDirective, getRenderedText, templateRefExtractor} from '../../src/render3/index';
|
||||||
|
|
||||||
import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, listener, markDirty, reference, text, template, textBinding, tick} from '../../src/render3/instructions';
|
import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, listener, markDirty, reference, text, template, textBinding, tick} from '../../src/render3/instructions';
|
||||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||||
|
@ -562,6 +562,64 @@ describe('change detection', () => {
|
||||||
expect(getRenderedText(comp)).toEqual('1');
|
expect(getRenderedText(comp)).toEqual('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should not go infinite loop when recursively called from children\'s ngOnChanges', () => {
|
||||||
|
class ChildComp {
|
||||||
|
// @Input
|
||||||
|
inp = '';
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
constructor(public parentComp: ParentComp) {}
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
this.count++;
|
||||||
|
if (this.count > 1) throw new Error(`ngOnChanges should be called only once!`);
|
||||||
|
this.parentComp.triggerChangeDetection();
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: ChildComp,
|
||||||
|
selectors: [['child-comp']],
|
||||||
|
factory: () => new ChildComp(directiveInject(ParentComp as any)),
|
||||||
|
consts: 1,
|
||||||
|
vars: 0,
|
||||||
|
template: (rf: RenderFlags, ctx: ChildComp) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
text(0, 'foo');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inputs: {inp: 'inp'},
|
||||||
|
features: [NgOnChangesFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParentComp {
|
||||||
|
constructor(public cdr: ChangeDetectorRef) {}
|
||||||
|
|
||||||
|
triggerChangeDetection() { this.cdr.detectChanges(); }
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: ParentComp,
|
||||||
|
selectors: [['parent-comp']],
|
||||||
|
factory: () => new ParentComp(directiveInject(ChangeDetectorRef as any)),
|
||||||
|
consts: 1,
|
||||||
|
vars: 1,
|
||||||
|
/** {{ value }} */
|
||||||
|
template: (rf: RenderFlags, ctx: ParentComp) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
element(0, 'child-comp');
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
elementProperty(0, 'inp', bind(true));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directives: [ChildComp]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => renderComponent(ParentComp)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
it('should support call in ngDoCheck', () => {
|
it('should support call in ngDoCheck', () => {
|
||||||
class DetectChangesComp {
|
class DetectChangesComp {
|
||||||
doCheckCount = 0;
|
doCheckCount = 0;
|
||||||
|
|
Loading…
Reference in New Issue