From b2c12a0eeb77b043e3caf844df73c8a38f2e309f Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Fri, 20 Oct 2023 17:14:00 -0700 Subject: [PATCH] refactor(core): avoid invoking IntersectionObserver in defer triggers on the server This commit updates the logic to make sure that DOM-specific triggers do not produce errors on the server. Resolves #52304. --- .../core/src/render3/after_render_hooks.ts | 5 ++ .../platform-server/test/hydration_spec.ts | 51 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/core/src/render3/after_render_hooks.ts b/packages/core/src/render3/after_render_hooks.ts index 4662c4b711ab69..9b4038023975f6 100644 --- a/packages/core/src/render3/after_render_hooks.ts +++ b/packages/core/src/render3/after_render_hooks.ts @@ -148,6 +148,11 @@ export interface InternalAfterNextRenderOptions { export function internalAfterNextRender( callback: VoidFunction, options?: InternalAfterNextRenderOptions) { const injector = options?.injector ?? inject(Injector); + + // Similarly to the public `afterNextRender` function, an internal one + // is only invoked in a browser. + if (!isPlatformBrowser(injector)) return; + const afterRenderEventManager = injector.get(AfterRenderEventManager); afterRenderEventManager.internalCallbacks.push(callback); } diff --git a/packages/platform-server/test/hydration_spec.ts b/packages/platform-server/test/hydration_spec.ts index cbdb911864f3bf..6e3430cfd0c623 100644 --- a/packages/platform-server/test/hydration_spec.ts +++ b/packages/platform-server/test/hydration_spec.ts @@ -2374,6 +2374,57 @@ describe('platform-server hydration integration', () => { verifyClientAndSSRContentsMatch(ssrContents, clientRootNode); }); + it('should not reference IntersectionObserver on the server', async () => { + // This test verifies that there are no errors produced while rendering on a server + // when `on viewport` trigger is used for a defer block. + @Component({ + selector: 'my-lazy-cmp', + standalone: true, + template: 'Hi!', + }) + class MyLazyCmp { + } + + @Component({ + standalone: true, + selector: 'app', + imports: [MyLazyCmp], + template: ` + @defer (when isVisible; prefetch on viewport(ref)) { + + } @placeholder { +
Placeholder!
+ } + ` + }) + class SimpleComponent { + isVisible = false; + } + + const errors: string[] = []; + class CustomErrorHandler extends ErrorHandler { + override handleError(error: any): void { + errors.push(error); + } + } + const envProviders = [{ + provide: ErrorHandler, + useClass: CustomErrorHandler, + }]; + + const html = await ssr(SimpleComponent, undefined, envProviders); + const ssrContents = getAppContents(html); + expect(ssrContents).toContain(' { @Component({ selector: 'my-lazy-cmp',