Skip to content

Commit

Permalink
refactor(core): avoid invoking IntersectionObserver in defer triggers…
Browse files Browse the repository at this point in the history
… on the server

This commit updates the logic to make sure that DOM-specific triggers do not produce errors on the server.

Resolves angular#52304.
  • Loading branch information
AndrewKushnir committed Oct 21, 2023
1 parent 3278966 commit b2c12a0
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 0 deletions.
5 changes: 5 additions & 0 deletions packages/core/src/render3/after_render_hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
51 changes: 51 additions & 0 deletions packages/platform-server/test/hydration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
<my-lazy-cmp />
} @placeholder {
<div #ref>Placeholder!</div>
}
`
})
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('<app ngh');

// Even though trigger condition is `true`,
// the defer block remains in the "placeholder" mode on the server.
expect(ssrContents).toContain('Visible: true.');
expect(ssrContents).toContain('Placeholder');

// Verify that there are no errors.
expect(errors).toHaveSize(0);
});

it('should not hydrate when an entire block in skip hydration section', async () => {
@Component({
selector: 'my-lazy-cmp',
Expand Down

0 comments on commit b2c12a0

Please sign in to comment.