Skip to content

Control flow with defer inside does not clean up before httpClient ends its request #56992

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
zip-fa opened this issue Jul 15, 2024 · 19 comments
Closed
Labels
area: core Issues related to the framework runtime core: hydration needs: clarification This issue needs additional clarification from the reporter before the team can investigate.
Milestone

Comments

@zip-fa
Copy link

zip-fa commented Jul 15, 2024

Which @angular/* package(s) are the source of the bug?

core

Is this a regression?

Yes

Description

Hi. I noticed that defer's @Loading block is not cleaned up before HttpClient completes its request. In v17 it doesn't wait for it.
Demo video:

ng-bug.mov

Full component code which reproduces the issue:

import {Component, inject, PLATFORM_ID, signal} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {isPlatformServer} from "@angular/common";
import {LoggedCmp} from "./logged-cmp";
import {TestCmp} from "./test-cmp";
import {User} from "./types";

@Component({
  selector: 'app-home-cmp',
  standalone: true,
  imports: [
    LoggedCmp,
    TestCmp
  ],
  template: `
    @if (user()) {
      <p>
        @defer (when user()) {
          <app-logged-cmp [user]="user()!" />
        }
      </p>
    } @else {
      <div>
        @defer (on immediate) {
          <app-test-cmp />
        } @loading {
          <div>Loading....</div>
        } @placeholder {
          <div>Loading....</div>
        }
      </div>
    }
  `
})
export class HomeCmp {
  private readonly httpClient = inject(HttpClient);
  private readonly platformId = inject(PLATFORM_ID);

  user = signal<User | null>({id: 1, nickname: 'admin'});

  constructor() {
    if (isPlatformServer(this.platformId)) {
      this.user.set(null);
    } else {
      this.httpClient.get(`https://dummyjson.com/RESOURCE/?delay=5000&t=${ Date.now() }`)
        .subscribe();
    }
  }
}

Please provide a link to a minimal reproduction of the bug

https://github.com/zip-fa/ng18-defer-cleanup-issue

Please provide the exception or error you saw

"Loading..." block is not removed from DOM-tree before HttpClient completes request

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 18.1.0
Node: 22.4.1
Package Manager: npm 10.8.1
OS: darwin arm64

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

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1801.0
@angular-devkit/build-angular   18.1.0
@angular-devkit/core            18.1.0
@angular-devkit/schematics      18.1.0
@schematics/angular             18.1.0
rxjs                            7.8.1
typescript                      5.4.5
zone.js                         0.14.7

Anything else?

No response

@JeanMeche JeanMeche added core: defer Issues related to @defer blocks. area: core Issues related to the framework runtime labels Jul 16, 2024
@ngbot ngbot bot added this to the needsTriage milestone Jul 16, 2024
@JeanMeche
Copy link
Member

JeanMeche commented Jul 16, 2024

This is a hydration "issue".
The hydration only runs once the application becomes stable.
Invoking the request keeps the application unstable until the response arrives. Afaik, HttpRequests didn't contribute to stability prior to the experimental zoneless support.

@zip-fa
Copy link
Author

zip-fa commented Jul 23, 2024

Can this behavior be turned off on client side? I propose to disable ExperimentalPendingTasks for HttpClient on client side to fix this

@pkozlowski-opensource pkozlowski-opensource added bug and removed core: defer Issues related to @defer blocks. labels Jul 24, 2024
@pkozlowski-opensource
Copy link
Member

This is not related to defer. We should review the hydration strategy when it comes to view containers that have different views on client and server.

@pkozlowski-opensource pkozlowski-opensource added the P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent label Jul 24, 2024
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Jul 24, 2024
@AndrewKushnir AndrewKushnir added needs: clarification This issue needs additional clarification from the reporter before the team can investigate. and removed P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent bug labels Aug 8, 2024
@ngbot ngbot bot modified the milestones: Backlog, needsTriage Aug 8, 2024
@AndrewKushnir
Copy link
Contributor

AndrewKushnir commented Aug 8, 2024

@zip-fa looking at the video that you've shared and the code, Angular seems to work as expected.

The "Loading..." text that you see when you load the page is coming from the @placeholder block, which gets rendered on the server. After that, on the client, an application doesn't enter the "stable" state until HTTP request is in progress (in zone-based apps, HttpClient requests contribute to app stability). Once the request is complete and the app enters the "stable" state, Angular starts post-hydration cleanup, which removes unclaimed dehydrated views (removes the dehydrated view that contains the "Loading..." text).

@zip-fa
Copy link
Author

zip-fa commented Aug 8, 2024

@zip-fa looking at the video that you've shared and the code, Angular seems to work as expected.

The "Loading..." text that you see when you load the page is coming from the @placeholder block, which gets rendered on the server. After that, on the client, an application doesn't enter the "stable" state until HTTP request is in progress (in zone-based apps, HttpClient requests contribute to app stability). Once the request is complete and the app enters the "stable" state, Angular starts post-hydration cleanup, which removes unclaimed dehydrated views (removes the dehydrated view that contains the "Loading..." text).

I understand how it works, but this behavior slightly differs from the previous version: the HTTP client in v17 does not contribute to app stability, and blocks disappear instantly.
This is kinda breaking for me, when app changes its behaviour that much.

The only way to ‘fix this’ is to send all queries on the server side, so the client app will catch up with the response from the transfer state.

@JeanMeche JeanMeche added the bug label Aug 8, 2024
@JeanMeche
Copy link
Member

We did discuss this during triage, this not a defer blug but a hydration one. if/else block aren't handled correctly.

    @if (user()) {
      <div>foo</div>
    } @else {
      <div>bar</div>
    }

If <div>bar</div> is generated by the server, during hydration it doesn't replace it with the `

foo
generated client side and both are kept until the hydration cleanup.

@AndrewKushnir
Copy link
Contributor

AndrewKushnir commented Aug 9, 2024

this not a defer blug but a hydration one. if/else block aren't handled correctly.
If

bar
is generated by the server, during hydration it doesn't replace it with the foo
generated client side and both are kept until the hydration cleanup.

@JeanMeche all unclaimed dehydrated views as removed during the post-hydration cleanup (that's the goal of the cleanup). There might be a period of time when both branches are visible on a page, but the post-hydration cleanup should remove one of the branches and it looks like it's happening (if I understand the comment correctly). Just want to confirm: do you see that the if-else block gets into the correct state, just not immediately?

@AndrewKushnir
Copy link
Contributor

Quick update: there were some hydration cleanup fixes landed in v18.2.0. @zip-fa could you please try to update your app and check if there were any changes in hydration behavior in your use-case?

@zip-fa
Copy link
Author

zip-fa commented Aug 15, 2024

Sure thing! Will try new version tomorrow early

@zip-fa
Copy link
Author

zip-fa commented Aug 16, 2024

The issue still persists on v18.2

@zip-fa
Copy link
Author

zip-fa commented Sep 3, 2024

Hey guys. Just saw tweet about i18n hydration. Is there any chances this gets fixed?😔
Sorry for being annoying

@AndrewKushnir AndrewKushnir removed the bug label Sep 21, 2024
@AndrewKushnir
Copy link
Contributor

AndrewKushnir commented Sep 21, 2024

@zip-fa I've looked at provided repro and noticed the following hydration behavior on the client:

  • an application hydrates initially as needed, finding the necessary DOM nodes
  • the state of the user signal in the HomeCmp is different on the server and on the client
  • as a result, in the generated HTML, we see the Loading... text from the @else block
  • during hydration on the client, Angular detects that it needs to take a different path and render the @if part (since user info is available)
  • as a result, we see both Loading... and user information on the screen
  • once an application becomes stable (in our case - when HTTP call is completed) - Angular finds all unclaimed views and removes them (removing unclaimed DOM nodes)

The cleanup intentionally happens once an application becomes stable (e.g. when there is no pending work), but not immediately, since application state may change as a result of the pending work. Removing unclaimed views earlier may result in flickering on a page (for ex. if a user value would change multiple times during app initialization). The goal of the cleanup mechanism is to do a final sync between Angular's internal state and the state in the DOM. It's not recommended to rely on this cleanup for core application logic and the recommended approach is to have server and client representations as close as possible.

@zip-fa
Copy link
Author

zip-fa commented Sep 21, 2024

To make application behave similar on server & client side, we need to send requests inside SSR environment with user's JWT token. Currently, it's possible to do that in production build (intercept cookies via middleware on express side & provide JWT token as injection token), but it's not possible to do the same on local dev server. Is there a way to intercept request in dev environment (vite) with some kind of middleware @AndrewKushnir? If yes, this issue does not make sense

@AndrewKushnir
Copy link
Contributor

Is there a way to intercept request in dev environment (vite) with some kind of middleware

@zip-fa I believe it should be possible once PR angular/angular-cli#28463 lands (cc @alan-agius4 to confirm).

@alan-agius4
Copy link
Contributor

That is correct @AndrewKushnir.

@zip-fa
Copy link
Author

zip-fa commented Sep 21, 2024

Waiting to try v19 then 🙏🏻
Will update this issue if v19 is enough, thanks guys

@zip-fa
Copy link
Author

zip-fa commented Oct 3, 2024

On v19 this is no longer an issue - we can fully sync Client & Server rendering.

@zip-fa zip-fa closed this as completed Oct 3, 2024
@gabrielpuddo
Copy link

Please, solve this in version 18.2

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Nov 11, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime core: hydration needs: clarification This issue needs additional clarification from the reporter before the team can investigate.
Projects
None yet
Development

No branches or pull requests

6 participants
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