Skip to content
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

ApplicationRef.isStable() is not emitting? #183

Open
ejthan opened this issue Jun 14, 2024 · 11 comments
Open

ApplicationRef.isStable() is not emitting? #183

ejthan opened this issue Jun 14, 2024 · 11 comments

Comments

@ejthan
Copy link

ejthan commented Jun 14, 2024

Which @ngneat/query-* package(s) are the source of the bug?

query

Is this a regression?

Yes

Description

If i use this library with provideClientHydration (ssr) i get the following message after 10 seconds:

NG0506: Angular hydration expected the ApplicationRef.isStable() to emit true, but it didn't happen within 10000ms. Angular hydration logic depends on the application becoming stable as a signal to complete hydration process.

There is a macrotask "setTimeout" which causes this.
Here is the stack:

Error: Task 'macroTask' from 'setTimeout'.
at TaskTrackingZoneSpec.onScheduleTask (http://localhost:4200/@fs/xxx/.angular/cache/17.1.1/vite/deps/zone__js_plugins_task-tracking.js?v=61f6983d:25:32)
at _ZoneDelegate.scheduleTask (http://localhost:4200/polyfills.js:305:43)
at Object.onScheduleTask (http://localhost:4200/polyfills.js:229:61)
at _ZoneDelegate.scheduleTask (http://localhost:4200/polyfills.js:305:43)
at _Zone.scheduleTask (http://localhost:4200/polyfills.js:172:35)
at _Zone.scheduleMacroTask (http://localhost:4200/polyfills.js:190:19)
at scheduleMacroTaskWithCurrentZone (http://localhost:4200/polyfills.js:541:23)
at http://localhost:4200/polyfills.js:1991:20
at proto. (http://localhost:4200/polyfills.js:780:16)
at Query.scheduleGc (http://localhost:4200/@fs/xxx/.angular/cache/17.1.1/vite/deps/chunk-HRNDKNRI.js?v=8c38df9a:540:25)
at Object.onSuccess (http://localhost:4200/@fs/xxx/.angular/cache/17.1.1/vite/deps/chunk-HRNDKNRI.js?v=8c38df9a:793:16)
at resolve (http://localhost:4200/@fs/xxx/.angular/cache/17.1.1/vite/deps/chunk-HRNDKNRI.js?v=8c38df9a:373:25)
at _ZoneDelegate.invoke (http://localhost:4200/polyfills.js:294:158)
at Object.onInvoke (http://localhost:4200/@fs/xxx/.angular/cache/17.1.1/vite/deps/chunk-PQEXWVRU.js?v=8c38df9a:9311:25)
at _ZoneDelegate.invoke (http://localhost:4200/polyfills.js:294:46)
at _Zone.run (http://localhost:4200/polyfills.js:100:35)
at http://localhost:4200/polyfills.js:1028:28
at _ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:320:171)
at http://localhost:4200/@fs/xxx/.angular/cache/17.1.1/vite/deps/chunk-PQEXWVRU.js?v=8c38df9a:9129:49
at AsyncStackTaggingZoneSpec.onInvokeTask (http://localhost:4200/@fs/xxx/.angular/cache/17.1.1/vite/deps/chunk-PQEXWVRU.js?v=8c38df9a:9129:30)
at _ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:320:54)
at TaskTrackingZoneSpec.onInvokeTask (http://localhost:4200/@fs/xxx/.angular/cache/17.1.1/vite/deps/zone__js_plugins_task-tracking.js?v=61f6983d:50:31)
at _ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:320:54)
at Object.onInvokeTask (http://localhost:4200/@fs/xxx/.angular/cache/17.1.1/vite/deps/chunk-PQEXWVRU.js?v=8c38df9a:9300:25)
at _ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:320:54)
at _Zone.runTask (http://localhost:4200/polyfills.js:137:37)
at drainMicroTaskQueue (http://localhost:4200/polyfills.js:475:23)

Looks like Query.scheduleGc is the problem.

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

NG0506: Angular hydration expected the ApplicationRef.isStable() to emit `true`, but it didn't happen within 10000ms. Angular hydration logic depends on the application becoming stable as a signal to complete hydration process.

Please provide the environment you discovered this bug in

No response

Anything else?

No response

Do you want to create a pull request?

Yes

@NetanelBasal
Copy link
Member

You're welcome to create a PR

@luii
Copy link
Contributor

luii commented Jun 14, 2024

@NetanelBasal Do you think that we could just exclude the whole lib/or only the server part from NgZone? For the experimental NoOp Zone in v18 it should imo work just fine. For <= v17 it should be excluded from the zone maybe by wrapping the provider?

It would probably be the second inject that should be excluded right?

factory() {
if (isPlatformBrowser(inject(PLATFORM_ID))) {
inject(QueryClientMount);
}
return inject(QueryClientToken);
},
},
);

@ejthan
Copy link
Author

ejthan commented Jun 15, 2024

Is your suggestion to do something like this?

return inject(NgZone).runOutsideAngular(() => inject(QueryClientToken))

@luii
Copy link
Contributor

luii commented Jun 15, 2024

Yes, since NgZone is picking up on the timers from the Garbage Collector of Tanstack, this would probably work.
I dont see any problem with this approach, tanstack has no relations to angular itself, the only thing that is being used here are the signals, which, fortunately enough, work without having the zone enabled. Same goes for the observables, these should also run without ngzone.

Baseline being: either exclude them from the zone or patch out only the the timers from it

@ejthan
Copy link
Author

ejthan commented Jun 15, 2024

I have tested the whole thing with the following changes, but nothing has changed and I still get the same error message.

const QueryClientService = new InjectionToken<QueryClient>(
  'QueryClientService',
  {
    providedIn: 'root',
    factory() {
      if (isPlatformBrowser(inject(PLATFORM_ID))) {
        inject(QueryClientMount);
      }

      return inject(NgZone).runOutsideAngular(() => inject(QueryClientToken));
    }
  }
);

I tried that too:

const QueryClientToken = new InjectionToken<QueryClient>('QueryClient', {
  providedIn: 'root',
  factory() {
    return inject(NgZone).runOutsideAngular(() => new QueryClient(inject(QUERY_CLIENT_OPTIONS)));
  }
});

@luii
Copy link
Contributor

luii commented Jun 17, 2024

I cant think of any other thing then, which could fix that, this is also Angular's recommendation on their new documentation: https://angular.dev/errors/NG0506#third-party-libraries

@radekdob
Copy link

radekdob commented Jan 1, 2025

i think i have similar issue, so i switched to experimental zone less and the problem from angular is gone, but there was another one with waiting for query results to finish fetching, so my very first not deeply tested solution is
https://angular.dev/guide/experimental/zoneless#pendingtasks-for-server-side-rendering-ssr

 return this.#query({
      queryKey: [ChatQueryKeys.chatGroupsStats] as const,
      queryFn: () => {
        const taskCleanup = this.taskService.add();
        return this.chatRepository.fetchChatGroupsStats().pipe(
          delay(5000),
          tap((resp) => {
            setTimeout( () => taskCleanup(), 0)
          })
        );
      }
    }).result;

so it waits 5s on the server before marking the app as stable, so all needed data is there.
Seems it is doing its job, but not really like it ...

@luii
Copy link
Contributor

luii commented Jan 1, 2025

Could you briefly explain me why you're queue'ing a new macrotask (i.e. setTimeout( () => taskCleanup(), 0) after your response arrived?
Iirc observeOn(asapScheduler) can also be used right before you execute the tap's side effect since its downstreaming. (But keep in mind that when you want to reuse the pipe somewhere else that you switch back the scheduler afterwards):

return this.chatRepository.fetchChatGroupsStats().pipe(
  delay(5000),
  observeOn(asapScheduler),
  tap((resp) => taskCleanup())

asapScheduler
overseveOn

@radekdob
Copy link

radekdob commented Jan 1, 2025

@luii I suspect that taskCleanup() must be somehow deferred because without that it is(probably) cleaned up before template consume that signal value and server marks app as stable a bit to early(so data is fetched, but templated does not render value from it).

observeOn(asapScheduler) yeah, that also works. Thanks for hint, I see this operator for the first time and seems it is better than setTimeout because it should be executed earlier than macro task.

@luii
Copy link
Contributor

luii commented Jan 1, 2025

Okay if i see and understand that all correctly, tanstack is doing everything right but it's not blocking the ApplicationRef from becoming stable as long as your values haven't yet arrived, even with PendingTasks involved, is that correct?

What i believe the problem currently is, is that you're missing some initialData and your query then returns the loading state, though im very inexpierienced with SSR (and haven't had much time to invest into it) i know that the HttpClient under the hood uses PendingTasks to block ApplicationRef from becoming stable (whilst a HEAD or GET is in action (POST can be added too)). You're trying to stop it from becoming stable and wait for the whole data, if you provide initial data (which could be an empty array, or somithing similiar) then you can render everthing, and get subsequent partial updates from the server. Atleast this is what i think you want to do, correct?

From what is written in the Tanstack Docs its appearent that they also use initialData to prepopulate their queries.

The quickest way to get started is to not involve React Query at all when it comes to prefetching and not use the dehydrate/hydrate APIs. What you do instead is passing the raw data in as the initialData option to useQuery.

@radekdob
Copy link

radekdob commented Jan 2, 2025

Yes, if the query is fast enough so sometimes it on time before making app as stable and gets rendered in initial html, but sometimes not or simply other async task take longer and block app from becoming stable :)

Angular SSR itself currently does not support concept of server side props, so what is left it is hydrate and transfer state.
IMO passing initial data to every query might be a bit messy.

What i wanted to achieve is fetch chat unread messages on the server, render html with navbar including unread counter and use new partial hydration to not hydrate whole navbar straightaway.

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

No branches or pull requests

4 participants