From 07afc2bafd329e0ff1c8888622f9e1f3cb7e3c34 Mon Sep 17 00:00:00 2001 From: SkyZeroZx <73321943+SkyZeroZx@users.noreply.github.com> Date: Thu, 13 Aug 2026 11:27:05 -0500 Subject: [PATCH] feat(core): add `scrollMargin` to `@defer` viewport options Allow `scrollMargin` to be configured for `@defer` viewport, prefetch, and hydrate triggers. Unlike `rootMargin`, `scrollMargin` expands the clipping bounds of nested scroll containers. This allows deferred content to be loaded or prefetched before it enters the visible area of a nested scroller. Include `scrollMargin` in the IntersectionObserver cache key so viewport triggers with different margins use the correct observer configuration. --- adev/src/content/guide/templates/defer.md | 6 +- .../typecheck/test/type_check_block_spec.ts | 4 +- .../GOLDEN_PARTIAL.js | 12 +- ...ferred_hydrate_on_viewport_with_options.ts | 2 +- ...drate_on_viewport_with_options_template.js | 2 +- .../deferred_on_viewport_with_options.ts | 2 +- ...erred_on_viewport_with_options_template.js | 2 +- ...erred_prefetch_on_viewport_with_options.ts | 2 +- ...fetch_on_viewport_with_options_template.js | 2 +- .../test/ngtsc/template_typecheck_spec.ts | 2 +- .../render3/r3_template_transform_spec.ts | 8 +- .../core/primitives/defer/src/triggers.ts | 2 +- packages/core/test/acceptance/defer_spec.ts | 108 +++++++++++++++--- .../test/incremental_hydration_spec.ts | 12 +- 14 files changed, 125 insertions(+), 41 deletions(-) diff --git a/adev/src/content/guide/templates/defer.md b/adev/src/content/guide/templates/defer.md index ad714f3ee0ba..883057b46083 100644 --- a/adev/src/content/guide/templates/defer.md +++ b/adev/src/content/guide/templates/defer.md @@ -211,12 +211,14 @@ If you want to customize the options of the `IntersectionObserver`, the `viewpor
Hello!
-@defer (on viewport({trigger: greeting, rootMargin: '100px', threshold: 0.5})) { +@defer ( + on viewport({trigger: greeting, rootMargin: '100px', scrollMargin: '50px', threshold: 0.5}) +) { } -@defer (on viewport({rootMargin: '100px', threshold: 0.5})) { +@defer (on viewport({rootMargin: '100px', scrollMargin: '50px', threshold: 0.5})) { } @placeholder {
Implied trigger
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts index e8761743d4b4..d5934b137454 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts @@ -1955,7 +1955,7 @@ describe('type check blocks', () => { it('should generate options for `viewport` trigger', () => { const TEMPLATE = ` - @defer (on viewport({rootMargin: '123px'})) { + @defer (on viewport({rootMargin: '123px', scrollMargin: '456px'})) { {{main()}} } @placeholder {
{{placeholder()}}
@@ -1963,7 +1963,7 @@ describe('type check blocks', () => { `; expect(tcb(TEMPLATE)).toContain( - 'new IntersectionObserver(null!, ({ "rootMargin": "123px" })); "" + ((this).main()); "" + ((this).placeholder());', + 'new IntersectionObserver(null!, ({ "rootMargin": "123px", "scrollMargin": "456px" })); "" + ((this).main()); "" + ((this).placeholder());', ); }); }); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js index 70a0b8a70aa1..931ae397ee42 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js @@ -1399,7 +1399,7 @@ export class MyApp { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` {{message}} - @defer (on viewport({trigger: button, rootMargin: '123px', threshold: 59})) { + @defer (on viewport({trigger: button, rootMargin: '123px', scrollMargin: '456px', threshold: 59})) { {{message}} } @placeholder { @@ -1411,7 +1411,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE args: [{ template: ` {{message}} - @defer (on viewport({trigger: button, rootMargin: '123px', threshold: 59})) { + @defer (on viewport({trigger: button, rootMargin: '123px', scrollMargin: '456px', threshold: 59})) { {{message}} } @placeholder { @@ -1440,7 +1440,7 @@ export class MyApp { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` {{message}} - @defer (prefetch on viewport({trigger: button, rootMargin: '123px', threshold: 59})) { + @defer (prefetch on viewport({trigger: button, rootMargin: '123px', scrollMargin: '456px', threshold: 59})) { {{message}} } @placeholder { @@ -1452,7 +1452,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE args: [{ template: ` {{message}} - @defer (prefetch on viewport({trigger: button, rootMargin: '123px', threshold: 59})) { + @defer (prefetch on viewport({trigger: button, rootMargin: '123px', scrollMargin: '456px', threshold: 59})) { {{message}} } @placeholder { @@ -1481,7 +1481,7 @@ export class MyApp { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` {{message}} - @defer (hydrate on viewport({rootMargin: '123px', threshold: 59})) { + @defer (hydrate on viewport({rootMargin: '123px', scrollMargin: '456px', threshold: 59})) { {{message}} } `, isInline: true }); @@ -1491,7 +1491,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE args: [{ template: ` {{message}} - @defer (hydrate on viewport({rootMargin: '123px', threshold: 59})) { + @defer (hydrate on viewport({rootMargin: '123px', scrollMargin: '456px', threshold: 59})) { {{message}} } `, diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options.ts index 8240cbda7d64..a38d6fe31089 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options.ts @@ -3,7 +3,7 @@ import {Component} from '@angular/core'; @Component({ template: ` {{message}} - @defer (hydrate on viewport({rootMargin: '123px', threshold: 59})) { + @defer (hydrate on viewport({rootMargin: '123px', scrollMargin: '456px', threshold: 59})) { {{message}} } `, diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options_template.js index 00d4827c53ba..a56c4987c9ba 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options_template.js @@ -4,7 +4,7 @@ function MyApp_Template(rf, ctx) { $r3$.ɵɵdomTemplate(1, MyApp_Defer_1_Template, 1, 1); $r3$.ɵɵenableIncrementalHydrationRuntime(); $r3$.ɵɵdefer(2, 1, null, null, null, null, null, null, null, 1); - $r3$.ɵɵdeferHydrateOnViewport({rootMargin: "123px", threshold: 59}); + $r3$.ɵɵdeferHydrateOnViewport({rootMargin: "123px", scrollMargin: "456px", threshold: 59}); $r3$.ɵɵdeferOnIdle(); } if (rf & 2) { diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_viewport_with_options.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_viewport_with_options.ts index 6b7b8c5fed0f..d0215729bf70 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_viewport_with_options.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_viewport_with_options.ts @@ -3,7 +3,7 @@ import {Component} from '@angular/core'; @Component({ template: ` {{message}} - @defer (on viewport({trigger: button, rootMargin: '123px', threshold: 59})) { + @defer (on viewport({trigger: button, rootMargin: '123px', scrollMargin: '456px', threshold: 59})) { {{message}} } @placeholder { diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_viewport_with_options_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_viewport_with_options_template.js index 1531e2f9e4b1..595aef2c4e8b 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_viewport_with_options_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_viewport_with_options_template.js @@ -3,7 +3,7 @@ function MyApp_Template(rf, ctx) { $r3$.ɵɵtext(0); $r3$.ɵɵdomTemplate(1, MyApp_Defer_1_Template, 1, 1)(2, MyApp_DeferPlaceholder_2_Template, 3, 0); $r3$.ɵɵdefer(3, 1, null, null, 2); - $r3$.ɵɵdeferOnViewport(0, -1, {rootMargin: "123px", threshold: 59}); + $r3$.ɵɵdeferOnViewport(0, -1, {rootMargin: "123px", scrollMargin: "456px", threshold: 59}); } if (rf & 2) { $r3$.ɵɵtextInterpolate1(" ", ctx.message, " "); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_prefetch_on_viewport_with_options.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_prefetch_on_viewport_with_options.ts index 5af2213fde12..b144ed2fe152 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_prefetch_on_viewport_with_options.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_prefetch_on_viewport_with_options.ts @@ -3,7 +3,7 @@ import {Component} from '@angular/core'; @Component({ template: ` {{message}} - @defer (prefetch on viewport({trigger: button, rootMargin: '123px', threshold: 59})) { + @defer (prefetch on viewport({trigger: button, rootMargin: '123px', scrollMargin: '456px', threshold: 59})) { {{message}} } @placeholder { diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_prefetch_on_viewport_with_options_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_prefetch_on_viewport_with_options_template.js index eaa6d1ccbc34..c2920e0b86df 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_prefetch_on_viewport_with_options_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_prefetch_on_viewport_with_options_template.js @@ -3,7 +3,7 @@ function MyApp_Template(rf, ctx) { $r3$.ɵɵtext(0); $r3$.ɵɵdomTemplate(1, MyApp_Defer_1_Template, 1, 1)(2, MyApp_DeferPlaceholder_2_Template, 3, 0); $r3$.ɵɵdefer(3, 1, null, null, 2); - $r3$.ɵɵdeferPrefetchOnViewport(0, -1, {rootMargin: "123px", threshold: 59}); + $r3$.ɵɵdeferPrefetchOnViewport(0, -1, {rootMargin: "123px", scrollMargin: "456px", threshold: 59}); $r3$.ɵɵdeferOnIdle(); } if (rf & 2) { diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts index c2e2d10447bc..b5ae0c37e872 100644 --- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts @@ -5505,7 +5505,7 @@ suppress @Component({ template: \` - @defer (on viewport({trigger: target, rootMargin: '10px', doesNotExist: true})) { + @defer (on viewport({trigger: target, rootMargin: '10px', scrollMargin: '20px', doesNotExist: true})) { Content } diff --git a/packages/compiler/test/render3/r3_template_transform_spec.ts b/packages/compiler/test/render3/r3_template_transform_spec.ts index 59ac9b364e99..3ca6e6bb72e8 100644 --- a/packages/compiler/test/render3/r3_template_transform_spec.ts +++ b/packages/compiler/test/render3/r3_template_transform_spec.ts @@ -1384,10 +1384,14 @@ describe('R3 template transform', () => { it('should parse a viewport trigger with an options parameter', () => { expectFromHtml( - '@defer (on viewport({trigger: foo, rootMargin: "123px", threshold: [1, 2, 3]})){hello}', + '@defer (on viewport({trigger: foo, rootMargin: "123px", scrollMargin: "456px", threshold: [1, 2, 3]})){hello}', ).toEqual([ ['DeferredBlock'], - ['ViewportDeferredTrigger', 'foo', '{rootMargin: "123px", threshold: [1, 2, 3]}'], + [ + 'ViewportDeferredTrigger', + 'foo', + '{rootMargin: "123px", scrollMargin: "456px", threshold: [1, 2, 3]}', + ], ['Text', 'hello'], ]); }); diff --git a/packages/core/primitives/defer/src/triggers.ts b/packages/core/primitives/defer/src/triggers.ts index de2fd503ba63..3942cda5cd87 100644 --- a/packages/core/primitives/defer/src/triggers.ts +++ b/packages/core/primitives/defer/src/triggers.ts @@ -216,5 +216,5 @@ function getIntersectionObserverKey(options: IntersectionObserverInit | undefine return ''; } - return `${options.rootMargin}/${typeof options.threshold === 'number' ? options.threshold : options.threshold?.join('\n')}`; + return `${options.rootMargin}/${options.scrollMargin}/${typeof options.threshold === 'number' ? options.threshold : options.threshold?.join('\n')}`; } diff --git a/packages/core/test/acceptance/defer_spec.ts b/packages/core/test/acceptance/defer_spec.ts index f3b329167905..6c7e9dfbeca5 100644 --- a/packages/core/test/acceptance/defer_spec.ts +++ b/packages/core/test/acceptance/defer_spec.ts @@ -4589,7 +4589,9 @@ describe('@defer', () => { it('should take the `on viewport` options into account when creating IntersectionObserver', async () => { @Component({ template: ` - @defer (on viewport({trigger, rootMargin: '123px', threshold: 0.5})) { + @defer ( + on viewport({trigger, rootMargin: '123px', scrollMargin: '456px', threshold: 0.5}) + ) { Hello } @@ -4600,19 +4602,26 @@ describe('@defer', () => { class MyCmp {} const fixture = TestBed.createComponent(MyCmp); - fixture.detectChanges(); + await fixture.whenStable(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); expect(activeObservers.length).toBe(1); expect(activeObservers[0].observedElements.size).toBe(1); expect(activeObservers[0].observedElements.has(button)).toBe(true); - expect(activeObservers[0].options).toEqual({rootMargin: '123px', threshold: 0.5}); + expect(activeObservers[0].options).toEqual({ + rootMargin: '123px', + scrollMargin: '456px', + threshold: 0.5, + }); }); it('should take the `prefetch on viewport` options into account when creating IntersectionObserver', async () => { @Component({ template: ` - @defer (prefetch on viewport({trigger, rootMargin: '123px', threshold: 0.5})) { + @defer ( + on interaction(trigger); + prefetch on viewport({trigger, rootMargin: '123px', scrollMargin: '456px', threshold: 0.5}) + ) { Hello } @@ -4623,33 +4632,96 @@ describe('@defer', () => { class MyCmp {} const fixture = TestBed.createComponent(MyCmp); - fixture.detectChanges(); + await fixture.whenStable(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); expect(activeObservers.length).toBe(1); expect(activeObservers[0].observedElements.size).toBe(1); expect(activeObservers[0].observedElements.has(button)).toBe(true); - expect(activeObservers[0].options).toEqual({rootMargin: '123px', threshold: 0.5}); + expect(activeObservers[0].options).toEqual({ + rootMargin: '123px', + scrollMargin: '456px', + threshold: 0.5, + }); }); - it('should create different intersection observers depending on their options', async () => { + it('should create different intersection observers for different `rootMargin` values', async () => { @Component({ template: ` - @defer (on viewport(trigger)) { + @defer (on viewport({trigger, rootMargin: '123px'})) { One } - @defer (on viewport({trigger, rootMargin: '123px'})) { + @defer (on viewport({trigger, rootMargin: '1vh'})) { Two } - @defer (on viewport({trigger, rootMargin: '1vh'})) { + + + `, + + changeDetection: ChangeDetectionStrategy.Eager, + }) + class MyCmp {} + + const fixture = TestBed.createComponent(MyCmp); + await fixture.whenStable(); + + const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); + expect(activeObservers.length).toBe(2); + expect(activeObservers[0].observedElements.size).toBe(1); + expect(activeObservers[0].observedElements.has(button)).toBe(true); + expect(activeObservers[0].options).toEqual({rootMargin: '123px'}); + + expect(activeObservers[1].observedElements.size).toBe(1); + expect(activeObservers[1].observedElements.has(button)).toBe(true); + expect(activeObservers[1].options).toEqual({rootMargin: '1vh'}); + }); + + it('should create different intersection observers for different `scrollMargin` values', async () => { + @Component({ + template: ` + @defer (on viewport({trigger, scrollMargin: '1px'})) { + One + } + @defer (on viewport({trigger, scrollMargin: '2px'})) { + Two + } + + + `, + + changeDetection: ChangeDetectionStrategy.Eager, + }) + class MyCmp {} + + const fixture = TestBed.createComponent(MyCmp); + await fixture.whenStable(); + + const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); + expect(activeObservers.length).toBe(2); + expect(activeObservers[0].observedElements.size).toBe(1); + expect(activeObservers[0].observedElements.has(button)).toBe(true); + expect(activeObservers[0].options).toEqual({scrollMargin: '1px'}); + + expect(activeObservers[1].observedElements.size).toBe(1); + expect(activeObservers[1].observedElements.has(button)).toBe(true); + expect(activeObservers[1].options).toEqual({scrollMargin: '2px'}); + }); + + it('should reuse an intersection observer only when both margins match', async () => { + @Component({ + template: ` + @defer (on viewport({trigger, rootMargin: '123px', scrollMargin: '1px'})) { + One + } + @defer (on viewport({trigger, rootMargin: '123px', scrollMargin: '2px'})) { + Two + } + @defer (on viewport({trigger, rootMargin: '1vh', scrollMargin: '1px'})) { Three } - @defer (on viewport(trigger)) { + @defer (on viewport({trigger, rootMargin: '123px', scrollMargin: '1px'})) { One Duplicate } - @defer (on viewport({trigger, rootMargin: '123px'})) { - Two Duplicate - } `, @@ -4659,21 +4731,21 @@ describe('@defer', () => { class MyCmp {} const fixture = TestBed.createComponent(MyCmp); - fixture.detectChanges(); + await fixture.whenStable(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); expect(activeObservers.length).toBe(3); expect(activeObservers[0].observedElements.size).toBe(1); expect(activeObservers[0].observedElements.has(button)).toBe(true); - expect(activeObservers[0].options).toBe(null); + expect(activeObservers[0].options).toEqual({rootMargin: '123px', scrollMargin: '1px'}); expect(activeObservers[1].observedElements.size).toBe(1); expect(activeObservers[1].observedElements.has(button)).toBe(true); - expect(activeObservers[1].options).toEqual({rootMargin: '123px'}); + expect(activeObservers[1].options).toEqual({rootMargin: '123px', scrollMargin: '2px'}); expect(activeObservers[2].observedElements.size).toBe(1); expect(activeObservers[2].observedElements.has(button)).toBe(true); - expect(activeObservers[2].options).toEqual({rootMargin: '1vh'}); + expect(activeObservers[2].options).toEqual({rootMargin: '1vh', scrollMargin: '1px'}); }); it('should not attach observer if rendering manually', async () => { diff --git a/packages/platform-server/test/incremental_hydration_spec.ts b/packages/platform-server/test/incremental_hydration_spec.ts index 74a81426c7d6..f176bad4b893 100644 --- a/packages/platform-server/test/incremental_hydration_spec.ts +++ b/packages/platform-server/test/incremental_hydration_spec.ts @@ -1282,7 +1282,9 @@ describe('platform-server partial hydration integration', () => { selector: 'app', template: `
- @defer (hydrate on viewport({rootMargin: '123px', threshold: 0.5})) { + @defer ( + hydrate on viewport({rootMargin: '123px', scrollMargin: '456px', threshold: 0.5}) + ) {
defer block rendered!
} @placeholder { Outer block placeholder @@ -1300,7 +1302,7 @@ describe('platform-server partial hydration integration', () => { const ssrContents = getAppContents(html); expect(ssrContents).toContain( - '"__nghDeferData__":{"d0":{"r":1,"s":2,"t":[{"trigger":2,"intersectionObserverOptions":{"rootMargin":"123px","threshold":0.5}}]}}', + '"__nghDeferData__":{"d0":{"r":1,"s":2,"t":[{"trigger":2,"intersectionObserverOptions":{"rootMargin":"123px","scrollMargin":"456px","threshold":0.5}}]}}', ); // Internal cleanup before we do server->client transition in this test. @@ -1316,7 +1318,11 @@ describe('platform-server partial hydration integration', () => { await appRef.whenStable(); expect(activeObservers.length).toBe(1); - expect(activeObservers[0].options).toEqual({rootMargin: '123px', threshold: 0.5}); + expect(activeObservers[0].options).toEqual({ + rootMargin: '123px', + scrollMargin: '456px', + threshold: 0.5, + }); }); it('should create an IntersectionObserver for a nested routed viewport block', async () => {