Skip to content

Commit 3aa933a

Browse files
kristilwpkozlowski-opensource
authored andcommitted
fix(core): components marked for traversal resets reactive context (angular#61663)
when marked for traversal the reactive context has to be set to null to avoid inheriting the reactive context of the parent component PR Closes angular#61662 PR Close angular#61663
1 parent ee6388d commit 3aa933a

File tree

2 files changed

+62
-11
lines changed

2 files changed

+62
-11
lines changed

packages/core/src/render3/instructions/change_detection.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
consumerPollProducersForChange,
1414
getActiveConsumer,
1515
ReactiveNode,
16+
setActiveConsumer,
1617
} from '../../../primitives/signals';
1718

1819
import {RuntimeError, RuntimeErrorCode} from '../../errors';
@@ -207,7 +208,10 @@ export function refreshView<T>(
207208
} else if (getActiveConsumer() === null) {
208209
// If the current view should not have a reactive consumer but we don't have an active consumer,
209210
// we still need to create a temporary consumer to track any signal reads in this template.
210-
// This is a rare case that can happen with `viewContainerRef.createEmbeddedView(...).detectChanges()`.
211+
// This is a rare case that can happen with
212+
// - `viewContainerRef.createEmbeddedView(...).detectChanges()`.
213+
// - `viewContainerRef.createEmbeddedView(...)` without any other dirty marking on the parent,
214+
// flagging the parent component for traversal but not triggering a full `refreshView`.
211215
// This temporary consumer marks the first parent that _should_ have a consumer for refresh.
212216
// Once that refresh happens, the signals will be tracked in the parent consumer and we can destroy
213217
// the temporary one.
@@ -490,16 +494,22 @@ function detectChangesInView(lView: LView, mode: ChangeDetectionMode) {
490494
if (shouldRefreshView) {
491495
refreshView(tView, lView, tView.template, lView[CONTEXT]);
492496
} else if (flags & LViewFlags.HasChildViewsToRefresh) {
493-
if (!isInCheckNoChangesPass) {
494-
runEffectsInView(lView);
495-
}
496-
detectChangesInEmbeddedViews(lView, ChangeDetectionMode.Targeted);
497-
const components = tView.components;
498-
if (components !== null) {
499-
detectChangesInChildComponents(lView, components, ChangeDetectionMode.Targeted);
500-
}
501-
if (!isInCheckNoChangesPass) {
502-
addAfterRenderSequencesForView(lView);
497+
// Set active consumer to null to avoid inheriting an improper reactive context
498+
const prevConsumer = setActiveConsumer(null);
499+
try {
500+
if (!isInCheckNoChangesPass) {
501+
runEffectsInView(lView);
502+
}
503+
detectChangesInEmbeddedViews(lView, ChangeDetectionMode.Targeted);
504+
const components = tView.components;
505+
if (components !== null) {
506+
detectChangesInChildComponents(lView, components, ChangeDetectionMode.Targeted);
507+
}
508+
if (!isInCheckNoChangesPass) {
509+
addAfterRenderSequencesForView(lView);
510+
}
511+
} finally {
512+
setActiveConsumer(prevConsumer);
503513
}
504514
}
505515
}

packages/core/test/acceptance/change_detection_spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,47 @@ describe('change detection', () => {
222222

223223
expect(counter).toBe(3);
224224
});
225+
226+
it('updating signal inside an EmbeddedView in a child component with OnPush inside a parent component with Default CD', async () => {
227+
const data = signal('initial');
228+
229+
@Component({
230+
selector: 'child',
231+
template: '<ng-container *viewManipulation>{{data()}}</ng-container>',
232+
imports: [ViewManipulation],
233+
changeDetection: ChangeDetectionStrategy.OnPush,
234+
})
235+
class ChildComponent {
236+
data = data;
237+
}
238+
239+
@Component({
240+
template: '<child/>',
241+
changeDetection: ChangeDetectionStrategy.Default,
242+
imports: [ChildComponent],
243+
})
244+
class ParentComponent {}
245+
246+
TestBed.configureTestingModule({
247+
providers: [{provide: ComponentFixtureAutoDetect, useValue: true}],
248+
});
249+
250+
const fixture = TestBed.createComponent(ParentComponent);
251+
await fixture.whenStable();
252+
expect(fixture.nativeElement.innerText).toBe('');
253+
254+
fixture.debugElement
255+
.queryAllNodes(By.directive(ViewManipulation))[0]
256+
.injector.get(ViewManipulation)
257+
.insertIntoVcRef();
258+
await fixture.whenStable();
259+
expect(fixture.nativeElement.innerText).toBe(data());
260+
261+
data.set('new');
262+
expect(fixture.isStable()).toBe(false);
263+
await fixture.whenStable();
264+
expect(fixture.nativeElement.innerText).toBe(data());
265+
});
225266
});
226267

227268
describe('markForCheck', () => {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy