mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(compiler): support viewport trigger options in pipeline (#64130)
Updates the template pipeline to support options for the `viewport` triggers. PR Close #64130
This commit is contained in:
parent
e2367c8855
commit
ddeef60db2
12 changed files with 276 additions and 11 deletions
|
|
@ -1291,3 +1291,128 @@ export declare class TestCmp {
|
|||
static ɵcmp: i0.ɵɵComponentDeclaration<TestCmp, "test-cmp", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_on_viewport_with_options.js
|
||||
****************************************************************************************************/
|
||||
import { Component } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyApp {
|
||||
constructor() {
|
||||
this.message = 'hello';
|
||||
}
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵ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})) {
|
||||
{{message}}
|
||||
} @placeholder {
|
||||
<button #button>Click me</button>
|
||||
}
|
||||
`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: `
|
||||
{{message}}
|
||||
@defer (on viewport({trigger: button, rootMargin: '123px', threshold: 59})) {
|
||||
{{message}}
|
||||
} @placeholder {
|
||||
<button #button>Click me</button>
|
||||
}
|
||||
`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_on_viewport_with_options.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyApp {
|
||||
message: string;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_prefetch_on_viewport_with_options.js
|
||||
****************************************************************************************************/
|
||||
import { Component } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyApp {
|
||||
constructor() {
|
||||
this.message = 'hello';
|
||||
}
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵ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})) {
|
||||
{{message}}
|
||||
} @placeholder {
|
||||
<button #button>Click me</button>
|
||||
}
|
||||
`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: `
|
||||
{{message}}
|
||||
@defer (prefetch on viewport({trigger: button, rootMargin: '123px', threshold: 59})) {
|
||||
{{message}}
|
||||
} @placeholder {
|
||||
<button #button>Click me</button>
|
||||
}
|
||||
`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_prefetch_on_viewport_with_options.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyApp {
|
||||
message: string;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_hydrate_on_viewport_with_options.js
|
||||
****************************************************************************************************/
|
||||
import { Component } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyApp {
|
||||
constructor() {
|
||||
this.message = 'hello';
|
||||
}
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵ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})) {
|
||||
{{message}}
|
||||
}
|
||||
`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: `
|
||||
{{message}}
|
||||
@defer (hydrate on viewport({rootMargin: '123px', threshold: 59})) {
|
||||
{{message}}
|
||||
}
|
||||
`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_hydrate_on_viewport_with_options.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyApp {
|
||||
message: string;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -329,6 +329,51 @@
|
|||
"failureMessage": "Defer block output with import alias does not match deferred_import_alias.js (possible alias resolution regression)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should generate a defer block with a `on viewport` trigger that has options",
|
||||
"inputFiles": ["deferred_on_viewport_with_options.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"expected": "deferred_on_viewport_with_options_template.js",
|
||||
"generated": "deferred_on_viewport_with_options.js"
|
||||
}
|
||||
],
|
||||
"failureMessage": "Incorrect template"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should generate a defer block with a `prefetch on viewport` trigger that has options",
|
||||
"inputFiles": ["deferred_prefetch_on_viewport_with_options.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"expected": "deferred_prefetch_on_viewport_with_options_template.js",
|
||||
"generated": "deferred_prefetch_on_viewport_with_options.js"
|
||||
}
|
||||
],
|
||||
"failureMessage": "Incorrect template"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should generate a defer block with a `hydrate on viewport` trigger that has options",
|
||||
"inputFiles": ["deferred_hydrate_on_viewport_with_options.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"expected": "deferred_hydrate_on_viewport_with_options_template.js",
|
||||
"generated": "deferred_hydrate_on_viewport_with_options.js"
|
||||
}
|
||||
],
|
||||
"failureMessage": "Incorrect template"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
{{message}}
|
||||
@defer (hydrate on viewport({rootMargin: '123px', threshold: 59})) {
|
||||
{{message}}
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class MyApp {
|
||||
message = 'hello';
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtext(0);
|
||||
$r3$.ɵɵdomTemplate(1, MyApp_Defer_1_Template, 1, 1);
|
||||
$r3$.ɵɵdefer(2, 1, null, null, null, null, null, null, null, 1);
|
||||
$r3$.ɵɵdeferHydrateOnViewport({rootMargin: "123px", threshold: 59});
|
||||
$r3$.ɵɵdeferOnIdle();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
{{message}}
|
||||
@defer (on viewport({trigger: button, rootMargin: '123px', threshold: 59})) {
|
||||
{{message}}
|
||||
} @placeholder {
|
||||
<button #button>Click me</button>
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class MyApp {
|
||||
message = 'hello';
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$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});
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
{{message}}
|
||||
@defer (prefetch on viewport({trigger: button, rootMargin: '123px', threshold: 59})) {
|
||||
{{message}}
|
||||
} @placeholder {
|
||||
<button #button>Click me</button>
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class MyApp {
|
||||
message = 'hello';
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$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$.ɵɵdeferOnIdle();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
|
||||
}
|
||||
}
|
||||
|
|
@ -1408,6 +1408,7 @@ interface DeferInteractionTrigger extends DeferTriggerWithTargetBase {
|
|||
|
||||
interface DeferViewportTrigger extends DeferTriggerWithTargetBase {
|
||||
kind: DeferTriggerKind.Viewport;
|
||||
options: o.Expression | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -849,6 +849,9 @@ function ingestDeferTriggers(
|
|||
targetSlot: null,
|
||||
targetView: null,
|
||||
targetSlotViewSteps: null,
|
||||
options: triggers.viewport.options
|
||||
? convertAst(triggers.viewport.options, unit.job, triggers.viewport.sourceSpan)
|
||||
: null,
|
||||
},
|
||||
modifier,
|
||||
triggers.viewport.sourceSpan,
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ const deferTriggerToR3TriggerInstructionsMap = new Map([
|
|||
|
||||
export function deferOn(
|
||||
trigger: ir.DeferTriggerKind,
|
||||
args: (number | null)[],
|
||||
args: o.Expression[],
|
||||
modifier: ir.DeferOpModifierKind,
|
||||
sourceSpan: ParseSourceSpan | null,
|
||||
): ir.CreateOp {
|
||||
|
|
@ -383,11 +383,7 @@ export function deferOn(
|
|||
if (instructionToCall === undefined) {
|
||||
throw new Error(`Unable to determine instruction for trigger ${trigger}`);
|
||||
}
|
||||
return call(
|
||||
instructionToCall,
|
||||
args.map((a) => o.literal(a)),
|
||||
sourceSpan,
|
||||
);
|
||||
return call(instructionToCall, args, sourceSpan);
|
||||
}
|
||||
|
||||
export function projectionDef(def: o.Expression | null): ir.CreateOp {
|
||||
|
|
|
|||
|
|
@ -375,27 +375,44 @@ function reifyCreateOperations(unit: CompilationUnit, ops: ir.OpList<ir.CreateOp
|
|||
);
|
||||
break;
|
||||
case ir.OpKind.DeferOn:
|
||||
let args: (number | null)[] = [];
|
||||
let args: o.Expression[] = [];
|
||||
switch (op.trigger.kind) {
|
||||
case ir.DeferTriggerKind.Never:
|
||||
case ir.DeferTriggerKind.Idle:
|
||||
case ir.DeferTriggerKind.Immediate:
|
||||
break;
|
||||
case ir.DeferTriggerKind.Timer:
|
||||
args = [op.trigger.delay];
|
||||
args = [o.literal(op.trigger.delay)];
|
||||
break;
|
||||
case ir.DeferTriggerKind.Viewport:
|
||||
// `hydrate` triggers don't support targets.
|
||||
if (op.modifier === ir.DeferOpModifierKind.HYDRATE) {
|
||||
args = op.trigger.options ? [op.trigger.options] : [];
|
||||
} else {
|
||||
// The slots not being defined at this point is invalid, however we
|
||||
// catch it during type checking. Pass in null in such cases.
|
||||
args = [o.literal(op.trigger.targetSlot?.slot ?? null)];
|
||||
if (op.trigger.targetSlotViewSteps !== 0) {
|
||||
args.push(o.literal(op.trigger.targetSlotViewSteps));
|
||||
} else if (op.trigger.options) {
|
||||
args.push(o.literal(null));
|
||||
}
|
||||
if (op.trigger.options) {
|
||||
args.push(op.trigger.options);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ir.DeferTriggerKind.Interaction:
|
||||
case ir.DeferTriggerKind.Hover:
|
||||
case ir.DeferTriggerKind.Viewport:
|
||||
// `hydrate` triggers don't support targets.
|
||||
if (op.modifier === ir.DeferOpModifierKind.HYDRATE) {
|
||||
args = [];
|
||||
} else {
|
||||
// The slots not being defined at this point is invalid, however we
|
||||
// catch it during type checking. Pass in null in such cases.
|
||||
args = [op.trigger.targetSlot?.slot ?? null];
|
||||
args = [o.literal(op.trigger.targetSlot?.slot ?? null)];
|
||||
if (op.trigger.targetSlotViewSteps !== 0) {
|
||||
args.push(op.trigger.targetSlotViewSteps);
|
||||
args.push(o.literal(op.trigger.targetSlotViewSteps));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
Loading…
Reference in a new issue