Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions adev/src/content/guide/templates/defer.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,14 @@ If you want to customize the options of the `IntersectionObserver`, the `viewpor
<div #greeting>Hello!</div>
<!-- With options and a trigger -->
@defer (on viewport({trigger: greeting, rootMargin: '100px', threshold: 0.5})) {
@defer (
on viewport({trigger: greeting, rootMargin: '100px', scrollMargin: '50px', threshold: 0.5})
) {
<greetings-cmp />
}
<!-- With options and an implied trigger -->
@defer (on viewport({rootMargin: '100px', threshold: 0.5})) {
@defer (on viewport({rootMargin: '100px', scrollMargin: '50px', threshold: 0.5})) {
<greetings-cmp />
} @placeholder {
<div>Implied trigger</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1955,15 +1955,15 @@ 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 {
<div>{{placeholder()}}</div>
}
`;

expect(tcb(TEMPLATE)).toContain(
'new IntersectionObserver(null!, ({ "rootMargin": "123px" })); "" + ((this).main()); "" + ((this).placeholder());',
'new IntersectionObserver(null!, ({ "rootMargin": "123px", "scrollMargin": "456px" })); "" + ((this).main()); "" + ((this).placeholder());',
);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
<button #button>Click me</button>
Expand All @@ -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 {
<button #button>Click me</button>
Expand Down Expand Up @@ -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 {
<button #button>Click me</button>
Expand All @@ -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 {
<button #button>Click me</button>
Expand Down Expand Up @@ -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 });
Expand All @@ -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}}
}
`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
}
`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
<button #button>Click me</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, " ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
<button #button>Click me</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
8 changes: 6 additions & 2 deletions packages/compiler/test/render3/r3_template_transform_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
]);
});
Expand Down
2 changes: 1 addition & 1 deletion packages/core/primitives/defer/src/triggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')}`;
}
108 changes: 90 additions & 18 deletions packages/core/test/acceptance/defer_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
<button #trigger></button>
Expand All @@ -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
}
<button #trigger></button>
Expand All @@ -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'})) {

<button #trigger></button>
`,

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
}

<button #trigger></button>
`,

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
}

<button #trigger></button>
`,
Expand All @@ -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 () => {
Expand Down
12 changes: 9 additions & 3 deletions packages/platform-server/test/incremental_hydration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1282,7 +1282,9 @@ describe('platform-server partial hydration integration', () => {
selector: 'app',
template: `
<main>
@defer (hydrate on viewport({rootMargin: '123px', threshold: 0.5})) {
@defer (
hydrate on viewport({rootMargin: '123px', scrollMargin: '456px', threshold: 0.5})
) {
<article>defer block rendered!</article>
} @placeholder {
<span>Outer block placeholder</span>
Expand All @@ -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.
Expand All @@ -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 () => {
Expand Down
Loading