Skip to content

Commit e2367c8

Browse files
crisbetoAndrewKushnir
authored andcommitted
refactor(compiler-cli): type check viewport trigger options (angular#64130)
Updates the template type checker to check the options of the `viewport` trigger against `IntersectionObserver`. PR Close angular#64130
1 parent f320700 commit e2367c8

File tree

5 files changed

+82
-10
lines changed

5 files changed

+82
-10
lines changed

packages/compiler-cli/src/ngtsc/typecheck/src/oob.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
804804
trigger.sourceSpan,
805805
ts.DiagnosticCategory.Error,
806806
ngErrorCode(ErrorCode.DEFER_IMPLICIT_TRIGGER_MISSING_PLACEHOLDER),
807-
'Trigger with no parameters can only be placed on an @defer that has a @placeholder block',
807+
'Trigger with no target can only be placed on an @defer that has a @placeholder block',
808808
),
809809
);
810810
}
@@ -823,7 +823,7 @@ export class OutOfBandDiagnosticRecorderImpl implements OutOfBandDiagnosticRecor
823823
trigger.sourceSpan,
824824
ts.DiagnosticCategory.Error,
825825
ngErrorCode(ErrorCode.DEFER_IMPLICIT_TRIGGER_INVALID_PLACEHOLDER),
826-
'Trigger with no parameters can only be placed on an @defer that has a ' +
826+
'Trigger with no target can only be placed on an @defer that has a ' +
827827
'@placeholder block with exactly one root element node',
828828
),
829829
);

packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,6 +2121,34 @@ class TcbForOfOp extends TcbOp {
21212121
}
21222122
}
21232123

2124+
/**
2125+
* A `TcbOp` which can be used to type check the options of an `IntersectionObserver`.
2126+
*/
2127+
class TcbIntersectionObserverOp extends TcbOp {
2128+
constructor(
2129+
private tcb: Context,
2130+
private scope: Scope,
2131+
private options: AST,
2132+
) {
2133+
super();
2134+
}
2135+
2136+
override readonly optional = false;
2137+
2138+
override execute(): null {
2139+
const options = tcbExpression(this.options, this.tcb, this.scope);
2140+
const callback = ts.factory.createNonNullExpression(ts.factory.createNull());
2141+
const expression = ts.factory.createNewExpression(
2142+
ts.factory.createIdentifier('IntersectionObserver'),
2143+
undefined,
2144+
[callback, options],
2145+
);
2146+
2147+
this.scope.addStatement(ts.factory.createExpressionStatement(expression));
2148+
return null;
2149+
}
2150+
}
2151+
21242152
/**
21252153
* Overall generation context for the type check block.
21262154
*
@@ -3012,6 +3040,10 @@ class Scope {
30123040
this.opQueue.push(new TcbExpressionOp(this.tcb, this, triggers.when.value));
30133041
}
30143042

3043+
if (triggers.viewport !== undefined && triggers.viewport.options !== null) {
3044+
this.opQueue.push(new TcbIntersectionObserverOp(this.tcb, this, triggers.viewport.options));
3045+
}
3046+
30153047
if (triggers.hover !== undefined) {
30163048
this.validateReferenceBasedDeferredTrigger(block, triggers.hover);
30173049
}

packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,6 +1856,20 @@ describe('type check blocks', () => {
18561856

18571857
expect(tcb(TEMPLATE)).toContain('((this).shouldShow()) && (((this).isVisible));');
18581858
});
1859+
1860+
it('should generate options for `viewport` trigger', () => {
1861+
const TEMPLATE = `
1862+
@defer (on viewport({rootMargin: '123px'})) {
1863+
{{main()}}
1864+
} @placeholder {
1865+
<div>{{placeholder()}}</div>
1866+
}
1867+
`;
1868+
1869+
expect(tcb(TEMPLATE)).toContain(
1870+
'new IntersectionObserver(null!, { "rootMargin": "123px" }); "" + ((this).main()); "" + ((this).placeholder());',
1871+
);
1872+
});
18591873
});
18601874

18611875
describe('conditional blocks', () => {

packages/compiler-cli/test/ngtsc/defer_spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,7 +1521,7 @@ runInEachFileSystem(() => {
15211521
const diags = env.driveDiagnostics();
15221522
expect(diags.length).toBe(1);
15231523
expect(diags[0].messageText).toBe(
1524-
'Trigger with no parameters can only be placed on an @defer that has a @placeholder block',
1524+
'Trigger with no target can only be placed on an @defer that has a @placeholder block',
15251525
);
15261526
});
15271527

@@ -1539,7 +1539,7 @@ runInEachFileSystem(() => {
15391539
const diags = env.driveDiagnostics();
15401540
expect(diags.length).toBe(1);
15411541
expect(diags[0].messageText).toBe(
1542-
'Trigger with no parameters can only be placed on an @defer that has a @placeholder block',
1542+
'Trigger with no target can only be placed on an @defer that has a @placeholder block',
15431543
);
15441544
});
15451545

@@ -1557,7 +1557,7 @@ runInEachFileSystem(() => {
15571557
const diags = env.driveDiagnostics();
15581558
expect(diags.length).toBe(1);
15591559
expect(diags[0].messageText).toBe(
1560-
'Trigger with no parameters can only be placed on an @defer that has a @placeholder block with exactly one root element node',
1560+
'Trigger with no target can only be placed on an @defer that has a @placeholder block with exactly one root element node',
15611561
);
15621562
});
15631563

@@ -1575,7 +1575,7 @@ runInEachFileSystem(() => {
15751575
const diags = env.driveDiagnostics();
15761576
expect(diags.length).toBe(1);
15771577
expect(diags[0].messageText).toBe(
1578-
'Trigger with no parameters can only be placed on an @defer that has a @placeholder block with exactly one root element node',
1578+
'Trigger with no target can only be placed on an @defer that has a @placeholder block with exactly one root element node',
15791579
);
15801580
});
15811581

@@ -1593,7 +1593,7 @@ runInEachFileSystem(() => {
15931593
const diags = env.driveDiagnostics();
15941594
expect(diags.length).toBe(1);
15951595
expect(diags[0].messageText).toBe(
1596-
'Trigger with no parameters can only be placed on an @defer that has a @placeholder block',
1596+
'Trigger with no target can only be placed on an @defer that has a @placeholder block',
15971597
);
15981598
});
15991599

@@ -1611,7 +1611,7 @@ runInEachFileSystem(() => {
16111611
const diags = env.driveDiagnostics();
16121612
expect(diags.length).toBe(1);
16131613
expect(diags[0].messageText).toBe(
1614-
'Trigger with no parameters can only be placed on an @defer that has a @placeholder block with exactly one root element node',
1614+
'Trigger with no target can only be placed on an @defer that has a @placeholder block with exactly one root element node',
16151615
);
16161616
});
16171617

@@ -1629,7 +1629,7 @@ runInEachFileSystem(() => {
16291629
const diags = env.driveDiagnostics();
16301630
expect(diags.length).toBe(1);
16311631
expect(diags[0].messageText).toBe(
1632-
'Trigger with no parameters can only be placed on an @defer that has a @placeholder block with exactly one root element node',
1632+
'Trigger with no target can only be placed on an @defer that has a @placeholder block with exactly one root element node',
16331633
);
16341634
});
16351635

@@ -1650,7 +1650,7 @@ runInEachFileSystem(() => {
16501650
const diags = env.driveDiagnostics();
16511651
expect(diags.length).toBe(1);
16521652
expect(diags[0].messageText).toBe(
1653-
'Trigger with no parameters can only be placed on an @defer that has a @placeholder block with exactly one root element node',
1653+
'Trigger with no target can only be placed on an @defer that has a @placeholder block with exactly one root element node',
16541654
);
16551655
});
16561656
});

packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4910,6 +4910,32 @@ suppress
49104910
'Trigger cannot find reference "trigger".',
49114911
);
49124912
});
4913+
4914+
it('should check the options of the `viewport` trigger', () => {
4915+
env.write(
4916+
'test.ts',
4917+
`
4918+
import {Component} from '@angular/core';
4919+
4920+
@Component({
4921+
template: \`
4922+
@defer (on viewport({trigger: target, rootMargin: '10px', doesNotExist: true})) {
4923+
Content
4924+
}
4925+
4926+
<div #target></div>
4927+
\`,
4928+
})
4929+
export class Main {}
4930+
`,
4931+
);
4932+
4933+
const diags = env.driveDiagnostics();
4934+
expect(diags.length).toBe(1);
4935+
expect(diags[0].messageText).toBe(
4936+
`Object literal may only specify known properties, and '"doesNotExist"' does not exist in type 'IntersectionObserverInit'.`,
4937+
);
4938+
});
49134939
});
49144940

49154941
describe('conditional blocks', () => {

0 commit comments

Comments
 (0)