Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(core): components marked for traversal resets reactive context #61663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

kristilw
Copy link
Contributor

@kristilw kristilw commented May 23, 2025

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 #61662

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.dev application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Issue Number: #61662

What is the new behavior?

The embedded view updates its template when it depends on a signal which has changed its value.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

@angular-robot angular-robot bot added the area: core Issues related to the framework runtime label May 23, 2025
@ngbot ngbot bot added this to the Backlog milestone May 23, 2025
@kristilw kristilw force-pushed the bug-change-detection branch from 0662b7f to cbd8381 Compare May 26, 2025 08:04
@kristilw kristilw changed the title test(core): add test for change detction bug fix(core): components marked for traversal resets reactive context May 26, 2025
@kristilw kristilw force-pushed the bug-change-detection branch from cbd8381 to 5283e46 Compare May 26, 2025 08:09
@kristilw kristilw marked this pull request as ready for review May 26, 2025 08:09
@pullapprove pullapprove bot requested a review from thePunderWoman May 26, 2025 08:14
@thePunderWoman thePunderWoman requested review from atscott and removed request for thePunderWoman May 26, 2025 08:39
Copy link
Contributor

@atscott atscott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct in isolation.

(TMI disclaimer) I'm still not sure about how I feel about the attached view case in general. It wasn't the intent that attaching a view would skip refreshing the component. Skipping it will result in its queries not being updated. E.g. if I add a test like this:

     @Directive({
          selector: '[step]'
        })
        class Step {}
    
        @Directive({selector: '[dataDirective]'})
        class DataDirective {
          readonly templateRef = inject(TemplateRef);
          readonly viewContainerRef = inject(ViewContainerRef);
    
          drawTemplate() {
            this.viewContainerRef.createEmbeddedView(this.templateRef, {$implicit: data});
          }
        }
    
        @Component({
          selector: 'child',
          template: '<ng-container *dataDirective="let data"><div step> {{data()}}</div></ng-container>',
          imports: [DataDirective, Step],
          changeDetection: ChangeDetectionStrategy.OnPush,
        })
        class ChildComponent {
          @ViewChildren(Step) steps?: QueryList<Step>;
          signalSteps = viewChildren(DataDirective);
        }
        ...

The length of steps and signalSteps should really both be 1 after you call drawTemplate and wait for stability, but it's not because view queries are updated at the end of refreshing the component view:

const viewQuery = tView.viewQuery;
if (viewQuery !== null) {
executeViewQueryFn<T>(RenderFlags.Update, viewQuery, context);
}

This is one of the reasons we ensure that embedded views share consumers with their parent component and why the temporary consumer marks the component view for refresh. The "correct" thing to do for this case would be to ensure that the component gets refreshed when an embedded view is attached but that comes with its own difficulties. There are similar problems with the old style queries that aren't necessarily solvable and we can kind of shrug and say to use signal queries to fix those problems.

This issue already exists and again the change here is still correct and doesn't introduce any regression. But I think it's worth noting that this "fixes" the issue in a way that's not necessarily directly addressing the problem with the attached view not marking its parent OnPush component for check.

}
if (!isInCheckNoChangesPass) {
addAfterRenderSequencesForView(lView);
// Sets active consumer to null, ensuring that an improper reactive context is not inherited
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Sets active consumer to null, ensuring that an improper reactive context is not inherited
// Set active consumer to null to avoid inheriting an improper reactive context

optional: Slight restructure to active voice and remove double negative. I think it's a bit easier to read this way but not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, updated

if (!isInCheckNoChangesPass) {
addAfterRenderSequencesForView(lView);
// Sets active consumer to null, ensuring that an improper reactive context is not inherited
const prevConsumer = setActiveConsumer(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't comment on unchanged lines, but in refreshView, we should also update the comment that explains when getActiveConsumer() === null. There are now more situations where the consumer is null than the viewContainerRef.createEmbeddedView(...).detectChanges() edge case. That's absolutely correct -- we just need to update the comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Just like to add that getActiveConsumer() === null would already be the case if you take my original bug report and add OnPush change detection to the ParentComponent

@@ -838,6 +840,51 @@ describe('Angular with scheduler and ZoneJS', () => {
expect(fixture.nativeElement.innerText).toContain('new');
});

it('updating signal inside an EmbeddedView in a child component with OnPush inside a parent component with Default CD', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test would be better in core/test/acceptance/change_detection_spec.ts, maybe under the describe('embedded views', ... or describe('OnPush', ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the test. Also updated the tests in describe('embedded views', ... to use standalone syntax while I was at it

Copy link
Contributor Author

@kristilw kristilw May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also not entirely satisfied with the test name. It feels a bit long, and it does not even convey that the child component has to be only marked for traversal and not dirty. Not sure if best practice in this repo is to have a long test name or add a comment to the test. I'll take your suggestion If you have any.

Copy link
Contributor

@atscott atscott May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about "does not inherit reactive consumer from ancestor when attached to non-dirty parent"?

Edit: That's pretty implementation-specific but I don't know what else to put. Maybe something along those lines but slightly different...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it was my repo I would rather have a longer test name. But I'm not deeply familiar with this repo, and I'm mostly concerned with fixing the bug, so I'll go with whatever makes you happy 😁 But I'll try to think of something

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't really matter. Long name is fine. I'm happy if you're happy.

update tests to use standalone components for easier test setup
@kristilw kristilw force-pushed the bug-change-detection branch from 5283e46 to 89de2d7 Compare May 27, 2025 22:26
@angular-robot angular-robot bot requested a review from atscott May 27, 2025 22:27
@kristilw
Copy link
Contributor Author

It says that the test job fails. I tried to rerun the one that failed locally but it runs without error. Is it just flaky?

@JeanMeche
Copy link
Member

Yes, the failed test was a flaky one.

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
@atscott atscott force-pushed the bug-change-detection branch from 89de2d7 to c3731e0 Compare May 29, 2025 23:04
@atscott atscott added the target: patch This PR is targeted for the next patch release label May 29, 2025
@JeanMeche
Copy link
Member

Presubmit is "green".

@JeanMeche JeanMeche added the action: merge The PR is ready for merge by the caretaker label Jun 4, 2025
@ngbot
Copy link

ngbot bot commented Jun 4, 2025

I see that you just added the action: merge label, but the following checks are still failing:
    failure status "google-internal-tests" is failing
    pending status "mergeability" is pending

If you want your PR to be merged, it has to pass all the CI checks.

If you can't get the PR to a green state due to flakes or broken main, please try rebasing to main and/or restarting the CI job. If that fails and you believe that the issue is not due to your change, please contact the caretaker and ask for help.

@pkozlowski-opensource
Copy link
Member

This PR was merged into the repository by commit 3aa933a.

The changes were merged into the following branches: main, 20.0.x

pkozlowski-opensource pushed a commit that referenced this pull request Jun 5, 2025
…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 #61662

PR Close #61663
pkozlowski-opensource pushed a commit that referenced this pull request Jun 5, 2025
update tests to use standalone components for easier test setup

PR Close #61663
pkozlowski-opensource pushed a commit that referenced this pull request Jun 5, 2025
…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 #61662

PR Close #61663
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
action: merge The PR is ready for merge by the caretaker area: core Issues related to the framework runtime target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Template with signal not updating when the value of the signal changes
4 participants