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

Skip to content

RxFor destroys/recreates DOM on long initial rendering, despite Angular non-destructive hydration was enabled #1867

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

Open
Platonn opened this issue Apr 2, 2025 · 5 comments · May be fixed by #1870

Comments

@Platonn
Copy link

Platonn commented Apr 2, 2025

Description

When the Server-Side-Rendered HTML is initially displayed in the browser, everything is visible.
And then CSR kicks in, and RxFor cuts long rendering task into smaller tasks (as intended).
But unfortunately Angular doesn't wait until all of DOM elements are rendered by RxFor and Angular already considers the hydration as done and destroys the Server-Side-Rendered DOM elements which were not yet rendered in CSR by RxFor. Therefore RxFor indirectly causes the DOM elements to be destroyed and recreated only later incrementally in separate tasks.
This might affect negatively CLS because the incrementally arriving components can cause layout shifts.

Steps to Reproduce the Issue

See the minimal app reproduction repo:
https://github.com/Platonn/test-rx-angular-non-destructive-hydration

For more, see screenshots

Initial SSR HTML displayed:

Image

... and then CSR kicks in, Angular destroys the DOM and RxFor renders DOM elements incrementally in separate tasks:

Image

Image

Image

Image

Image

Image

For reference, I'm attaching also an exported Performance trace from Chrome Dev Tools:
Trace-20250402T144018.json

Environment

@rx-angular/[email protected]
Angular CLI: 19.2.5
Node: 22.13.1
Package Manager: npm 10.9.2
OS: darwin arm64

Angular: 19.2.4
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, platform-server
... router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1902.5
@angular-devkit/build-angular   19.2.5
@angular-devkit/core            19.2.5
@angular-devkit/schematics      19.2.5
@angular/cli                    19.2.5
@angular/ssr                    19.2.5
@schematics/angular             19.2.5
rxjs                            7.8.2
typescript                      5.7.3
zone.js                         0.15.0
Google Chrome 134.0.6998.166 (Official Build) (arm64)
Revision 0b26d3a1ee1e44572492002c2e52ffcd13ac701b-refs/branch-heads/6998@{#2123}
OS macOS Version 14.6.1 (Build 23G93)
JavaScript V8 13.4.114.21
User agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
@Platonn
Copy link
Author

Platonn commented Apr 2, 2025

Known local workaround

In the app I can add the Input renderCallback to the RxFor loop and tell Angular's PendingTasks class (which is still in Developer Preview phase) to wait until the rendering is done.
Thanks to this the Angular's app won't be considered stable until RxFor finishes it's rendering of all elements.

*rxFor=".....; renderCallback: itemsRendered"
  itemsRendered = new Subject<Person[]>();
  pendingTasks = inject(PendingTasks);

  ngOnInit() {
    // Tell Angular to wait with considering the app as "stable"
    // until RxFor tells that all the items were rendered
    this.pendingTasks.run(() => firstValueFrom(this.itemsRendered));
  }

See full commit on my workaround branch: Platonn/test-rx-angular-non-destructive-hydration@8698bcc

Idea for solution inside RxAngular framework:

For better DX, instead of me explicitly binding the RxFor's renderCallback to Angukar's PendingTasks in each and every of my components that use RxFor, ideally RxAngular library should bind them together under the hood in each instance of RxFor (and RxLet I guess too? and other directives that schedule rendering asynchronously).

Such a DX improvent is even more important, when I'd have nested *rxFor loops in a single component's HTML template - then for my workaround I'd need manually to create renderCallbacks for each and every nested rxFor instance myself and then wait for completion of each of them.

Analisys of Angular source code that led to those ideas

For reference, see also Angular's source code:

Note: let's not confuse Browser's macro tasks with Angular's tasks mentioned above. Angular's PendingTasks are just a list of "todo" jobs that needs to be finished until Angular considers the app to be stable.

@Platonn
Copy link
Author

Platonn commented Apr 2, 2025

FYI: I've created a feature request in Angular repo to promote PendingTasks class to stable API (becasue it's still in developer preview as of today) - angular/angular#60697

@JeanMeche
Copy link
Contributor

angular/angular#60716 will promote the PendingTasks to stable in v20.

@hoebbelsB
Copy link
Member

@Platonn i'm deeply sorry to have you wait for so long. Thank you very much for the detailed issue description. I will have a look this week into it

@hoebbelsB hoebbelsB linked a pull request Apr 27, 2025 that will close this issue
@hoebbelsB
Copy link
Member

yoyo @Platonn I actually could keep my promise. Please see the PR ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants