mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat: support property interpolation in signal components
This commit is contained in:
-67
@@ -1,67 +0,0 @@
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: simple_dom_binding.js
|
||||
****************************************************************************************************/
|
||||
import { Component, signal } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class LiteralValueBinding {
|
||||
}
|
||||
LiteralValueBinding.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LiteralValueBinding, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
LiteralValueBinding.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: LiteralValueBinding, isSignal: true, selector: "app", ngImport: i0, template: `<button [disabled]="true"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LiteralValueBinding, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="true"></button>`,
|
||||
}]
|
||||
}] });
|
||||
export class FromContextBindingStatic {
|
||||
constructor() {
|
||||
this.isDisabled = true;
|
||||
}
|
||||
}
|
||||
FromContextBindingStatic.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingStatic, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
FromContextBindingStatic.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: FromContextBindingStatic, isSignal: true, selector: "app", ngImport: i0, template: `<button [disabled]="isDisabled"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingStatic, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="isDisabled"></button>`,
|
||||
}]
|
||||
}] });
|
||||
export class FromContextBindingSignal {
|
||||
constructor() {
|
||||
this.isDisabled = signal(true);
|
||||
}
|
||||
}
|
||||
FromContextBindingSignal.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingSignal, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
FromContextBindingSignal.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: FromContextBindingSignal, isSignal: true, selector: "app", ngImport: i0, template: `<button [disabled]="isDisabled()"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingSignal, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="isDisabled()"></button>`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: simple_dom_binding.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class LiteralValueBinding {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<LiteralValueBinding, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<LiteralValueBinding, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class FromContextBindingStatic {
|
||||
isDisabled: boolean;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<FromContextBindingStatic, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<FromContextBindingStatic, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class FromContextBindingSignal {
|
||||
isDisabled: import("@angular/core").WritableSignal<boolean>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<FromContextBindingSignal, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<FromContextBindingSignal, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"$schema": "../../test_case_schema.json",
|
||||
"cases": [
|
||||
{
|
||||
"onlyForTemplatePipeline": true,
|
||||
"description": "should support binding to DOM properties",
|
||||
"inputFiles": ["simple_dom_binding.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [{"generated": "simple_dom_binding.js", "expected": "simple_dom_binding.js"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: simple_binding.js
|
||||
****************************************************************************************************/
|
||||
import { Component, signal } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class LiteralValueBinding {
|
||||
}
|
||||
LiteralValueBinding.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LiteralValueBinding, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
LiteralValueBinding.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: LiteralValueBinding, isSignal: true, selector: "app", ngImport: i0, template: `<button [disabled]="true"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LiteralValueBinding, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="true"></button>`,
|
||||
}]
|
||||
}] });
|
||||
export class FromContextBindingStatic {
|
||||
constructor() {
|
||||
this.isDisabled = true;
|
||||
}
|
||||
}
|
||||
FromContextBindingStatic.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingStatic, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
FromContextBindingStatic.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: FromContextBindingStatic, isSignal: true, selector: "app", ngImport: i0, template: `<button [disabled]="isDisabled"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingStatic, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="isDisabled"></button>`,
|
||||
}]
|
||||
}] });
|
||||
export class FromContextBindingSignal {
|
||||
constructor() {
|
||||
this.isDisabled = signal(true);
|
||||
}
|
||||
}
|
||||
FromContextBindingSignal.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingSignal, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
FromContextBindingSignal.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: FromContextBindingSignal, isSignal: true, selector: "app", ngImport: i0, template: `<button [disabled]="isDisabled()"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingSignal, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="isDisabled()"></button>`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: simple_binding.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class LiteralValueBinding {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<LiteralValueBinding, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<LiteralValueBinding, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class FromContextBindingStatic {
|
||||
isDisabled: boolean;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<FromContextBindingStatic, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<FromContextBindingStatic, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class FromContextBindingSignal {
|
||||
isDisabled: import("@angular/core").WritableSignal<boolean>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<FromContextBindingSignal, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<FromContextBindingSignal, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: with_interpolation.js
|
||||
****************************************************************************************************/
|
||||
import { Component, signal } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class LiteralValueBinding {
|
||||
}
|
||||
LiteralValueBinding.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LiteralValueBinding, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
LiteralValueBinding.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: LiteralValueBinding, isSignal: true, selector: "app", ngImport: i0, template: `<button title="This is a {{'submit'}} button"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LiteralValueBinding, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{'submit'}} button"></button>`,
|
||||
}]
|
||||
}] });
|
||||
export class FromContextBindingStatic {
|
||||
constructor() {
|
||||
this.type = 'submit';
|
||||
}
|
||||
}
|
||||
FromContextBindingStatic.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingStatic, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
FromContextBindingStatic.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: FromContextBindingStatic, isSignal: true, selector: "app", ngImport: i0, template: `<button title="This is a {{type}} button"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingStatic, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{type}} button"></button>`,
|
||||
}]
|
||||
}] });
|
||||
export class FromContextBindingSignal {
|
||||
constructor() {
|
||||
this.type = signal('submit');
|
||||
}
|
||||
}
|
||||
FromContextBindingSignal.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingSignal, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
FromContextBindingSignal.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: FromContextBindingSignal, isSignal: true, selector: "app", ngImport: i0, template: `<button title="This is a {{type()}} button"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingSignal, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{type()}} button"></button>`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: with_interpolation.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class LiteralValueBinding {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<LiteralValueBinding, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<LiteralValueBinding, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class FromContextBindingStatic {
|
||||
type: string;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<FromContextBindingStatic, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<FromContextBindingStatic, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class FromContextBindingSignal {
|
||||
type: import("@angular/core").WritableSignal<string>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<FromContextBindingSignal, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<FromContextBindingSignal, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "../../test_case_schema.json",
|
||||
"cases": [
|
||||
{
|
||||
"onlyForTemplatePipeline": true,
|
||||
"description": "should support binding to DOM properties",
|
||||
"inputFiles": ["simple_binding.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [{"generated": "simple_binding.js", "expected": "simple_binding.js"}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"onlyForTemplatePipeline": true,
|
||||
"description": "should support interpolation binding to DOM properties",
|
||||
"inputFiles": ["with_interpolation.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [{"generated": "with_interpolation.js", "expected": "with_interpolation.js"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
template: function LiteralValueBinding_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵelementStart(0, "button");
|
||||
i0.ɵɵpropertyCreate(1, "title", function () { return i0.ɵɵstringifyInterpolation`This is a ${"submit"} button`; });
|
||||
i0.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
template: function FromContextBindingStatic_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵelementStart(0, "button");
|
||||
i0.ɵɵpropertyCreate(1, "title", function () { return i0.ɵɵstringifyInterpolation`This is a ${ctx.type} button`; });
|
||||
i0.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
template: function FromContextBindingSignal_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵelementStart(0, "button");
|
||||
i0.ɵɵpropertyCreate(1, "title", function () { return i0.ɵɵstringifyInterpolation`This is a ${ctx.type()} button`; });
|
||||
i0.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import {Component, signal} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{'submit'}} button"></button>`,
|
||||
})
|
||||
export class LiteralValueBinding {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{type}} button"></button>`,
|
||||
})
|
||||
export class FromContextBindingStatic {
|
||||
type = 'submit';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{type()}} button"></button>`,
|
||||
})
|
||||
export class FromContextBindingSignal {
|
||||
type = signal('submit');
|
||||
}
|
||||
@@ -182,6 +182,9 @@ export class Identifiers {
|
||||
static propertyCreate: o.ExternalReference = {name: 'ɵɵpropertyCreate', moduleName: CORE};
|
||||
static property: o.ExternalReference = {name: 'ɵɵproperty', moduleName: CORE};
|
||||
|
||||
static stringifyInterpolation:
|
||||
o.ExternalReference = {name: 'ɵɵstringifyInterpolation', moduleName: CORE};
|
||||
|
||||
static propertyInterpolate:
|
||||
o.ExternalReference = {name: 'ɵɵpropertyInterpolate', moduleName: CORE};
|
||||
static propertyInterpolate1:
|
||||
|
||||
@@ -236,7 +236,13 @@ export enum ExpressionKind {
|
||||
/**
|
||||
* A reference to a temporary variable.
|
||||
*/
|
||||
|
||||
ReadTemporaryExpr,
|
||||
/**
|
||||
* An interpolation template string expression. Such template string expression is
|
||||
* commonly expected to be part of the `PropertyCreate` operation expression.
|
||||
*/
|
||||
InterpolationTemplateExpr,
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,11 +18,11 @@ import {ConsumesVarsTrait, UsesSlotIndex, UsesSlotIndexTrait, UsesVarOffset, Use
|
||||
/**
|
||||
* An `o.Expression` subtype representing a logical expression in the intermediate representation.
|
||||
*/
|
||||
export type Expression =
|
||||
LexicalReadExpr|ReferenceExpr|ShallowReferenceExpr|ContextExpr|NextContextExpr|
|
||||
GetCurrentViewExpr|RestoreViewExpr|ResetViewExpr|ReadVariableExpr|PureFunctionExpr|
|
||||
PureFunctionParameterExpr|PipeBindingExpr|PipeBindingVariadicExpr|SafePropertyReadExpr|
|
||||
SafeKeyedReadExpr|SafeInvokeFunctionExpr|EmptyExpr|AssignTemporaryExpr|ReadTemporaryExpr;
|
||||
export type Expression = LexicalReadExpr|ReferenceExpr|ShallowReferenceExpr|ContextExpr|
|
||||
NextContextExpr|GetCurrentViewExpr|RestoreViewExpr|ResetViewExpr|ReadVariableExpr|
|
||||
PureFunctionExpr|PureFunctionParameterExpr|PipeBindingExpr|PipeBindingVariadicExpr|
|
||||
SafePropertyReadExpr|SafeKeyedReadExpr|SafeInvokeFunctionExpr|EmptyExpr|AssignTemporaryExpr|
|
||||
ReadTemporaryExpr|InterpolationTemplateExpr;
|
||||
|
||||
/**
|
||||
* Transformer type which converts expressions into general `o.Expression`s (which may be an
|
||||
@@ -661,6 +661,47 @@ export class SafeTernaryExpr extends ExpressionBase {
|
||||
}
|
||||
}
|
||||
|
||||
export class InterpolationTemplateExpr extends ExpressionBase {
|
||||
override readonly kind = ExpressionKind.InterpolationTemplateExpr;
|
||||
|
||||
constructor(public staticParts: string[], public expressionParts: o.Expression[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
override visitExpression() {
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
|
||||
override isEquivalent(e: o.Expression): boolean {
|
||||
if (!(e instanceof InterpolationTemplateExpr)) {
|
||||
return false;
|
||||
}
|
||||
if (e.staticParts.length !== this.staticParts.length) {
|
||||
return false;
|
||||
}
|
||||
if (e.expressionParts.length !== this.expressionParts.length) {
|
||||
return false;
|
||||
}
|
||||
return e.staticParts.every((p, i) => p === this.staticParts[i]) &&
|
||||
e.expressionParts.every((e, i) => e === this.expressionParts[i]);
|
||||
}
|
||||
|
||||
override isConstant(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
override transformInternalExpressions(transform: ExpressionTransform, flags: VisitorContextFlag):
|
||||
void {
|
||||
this.expressionParts =
|
||||
this.expressionParts.map(p => transformExpressionsInExpression(p, transform, flags));
|
||||
}
|
||||
|
||||
override clone(): InterpolationTemplateExpr {
|
||||
return new InterpolationTemplateExpr(
|
||||
[...this.staticParts], this.expressionParts.map(p => p.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptyExpr extends ExpressionBase {
|
||||
override readonly kind = ExpressionKind.EmptyExpr;
|
||||
|
||||
|
||||
@@ -272,11 +272,26 @@ function ingestPropertyBinding(
|
||||
}
|
||||
view.update.push(ir.createInterpolateStyleMapOp(
|
||||
xref, value.strings, value.expressions.map(expr => convertAst(expr, view.tpl))));
|
||||
} else {
|
||||
view.update.push(ir.createInterpolatePropertyOp(
|
||||
xref, bindingKind, name, value.strings,
|
||||
value.expressions.map(expr => convertAst(expr, view.tpl))));
|
||||
break;
|
||||
}
|
||||
|
||||
if (view.tpl.isSignal) {
|
||||
// Allocating an XRef ID because a property create operation
|
||||
// consumes a slot to store the expression.
|
||||
const propertyXrefId = view.tpl.allocateXrefId();
|
||||
|
||||
view.create.push(ir.createPropertyCreateOp(
|
||||
propertyXrefId, xref, name,
|
||||
new ir.InterpolationTemplateExpr(
|
||||
value.strings, value.expressions.map(e => convertAst(e, view.tpl)))));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
view.update.push(ir.createInterpolatePropertyOp(
|
||||
xref, bindingKind, name, value.strings,
|
||||
value.expressions.map(expr => convertAst(expr, view.tpl))));
|
||||
|
||||
break;
|
||||
case e.BindingType.Style:
|
||||
if (bindingKind !== ir.ElementAttributeKind.Binding) {
|
||||
|
||||
@@ -265,6 +265,13 @@ export function pureFunction(
|
||||
);
|
||||
}
|
||||
|
||||
export function stringifyInterpolation(
|
||||
staticParts: string[], expressions: o.Expression[]): o.Expression {
|
||||
return o.taggedTemplate(
|
||||
o.importExpr(Identifiers.stringifyInterpolation),
|
||||
new o.TemplateLiteral(staticParts.map(p => new o.TemplateLiteralElement(p)), expressions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collates the string an expression arguments for an interpolation instruction.
|
||||
*/
|
||||
|
||||
@@ -220,6 +220,8 @@ function reifyIrExpression(expr: o.Expression): o.Expression {
|
||||
return ng.pipeBind(expr.slot!, expr.varOffset!, expr.args);
|
||||
case ir.ExpressionKind.PipeBindingVariadic:
|
||||
return ng.pipeBindV(expr.slot!, expr.varOffset!, expr.args);
|
||||
case ir.ExpressionKind.InterpolationTemplateExpr:
|
||||
return ng.stringifyInterpolation(expr.staticParts, expr.expressionParts);
|
||||
default:
|
||||
throw new Error(`AssertionError: Unsupported reification of ir.Expression kind: ${
|
||||
ir.ExpressionKind[(expr as ir.Expression).kind]}`);
|
||||
|
||||
@@ -90,6 +90,7 @@ export const angularCoreEnv: {[name: string]: Function} =
|
||||
'ɵɵhostProperty': r3.ɵɵhostProperty,
|
||||
'ɵɵproperty': r3.ɵɵproperty,
|
||||
'ɵɵpropertyCreate': r3.ɵɵpropertyCreate,
|
||||
'ɵɵstringifyInterpolation': r3.ɵɵstringifyInterpolation,
|
||||
'ɵɵpropertyInterpolate': r3.ɵɵpropertyInterpolate,
|
||||
'ɵɵpropertyInterpolate1': r3.ɵɵpropertyInterpolate1,
|
||||
'ɵɵpropertyInterpolate2': r3.ɵɵpropertyInterpolate2,
|
||||
|
||||
@@ -60,67 +60,3 @@ describe('dom property bindings in signal based components', () => {
|
||||
expect(div.tabIndex).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dom property interpolation in signal based components', () => {
|
||||
xit('should support property interpolation in DOM bindings', () => {
|
||||
@Component({
|
||||
signals: true,
|
||||
template: `<div title="Hello, {{name()}}!"></div>`,
|
||||
standalone: true,
|
||||
})
|
||||
class App {
|
||||
name = signal('Angular');
|
||||
}
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const div = fixture.nativeElement.firstChild;
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(div.title).toBe('Hello, Angular!');
|
||||
});
|
||||
|
||||
describe('hand-generated code - DELETE WHEN COMPILER CAN GENERATE INTERPOLATION', () => {
|
||||
it('should support property interpolation in DOM bindings - hand generated code', () => {
|
||||
class TestComponent {
|
||||
name = signal('Angular');
|
||||
|
||||
static ɵfac = () => new TestComponent();
|
||||
static ɵcmp = ɵɵdefineComponent({
|
||||
type: TestComponent,
|
||||
selectors: [['test-cmp']],
|
||||
standalone: true,
|
||||
signals: true,
|
||||
features: [ɵɵStandaloneFeature],
|
||||
decls: 1,
|
||||
vars: 2,
|
||||
consts: [[3, 'title']],
|
||||
template:
|
||||
(rf: any, ctx: any) => {
|
||||
if ((rf & 1)) {
|
||||
ɵɵelement(0, 'div', 0);
|
||||
ɵɵpropertyCreate(
|
||||
1, 'title', () => ɵɵstringifyInterpolation`Hello, ${ctx.name()}!`);
|
||||
}
|
||||
},
|
||||
styles: []
|
||||
});
|
||||
}
|
||||
|
||||
@Component({
|
||||
signals: true,
|
||||
template: `<test-cmp />`,
|
||||
standalone: true,
|
||||
imports: [TestComponent],
|
||||
})
|
||||
class App {
|
||||
}
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const div = fixture.nativeElement.firstChild.firstChild;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(div.title).toBe('Hello, Angular!');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {USE_TEMPLATE_PIPELINE} from '@angular/compiler/src/template/pipeline/switch/index';
|
||||
import {Component, signal} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
if (!USE_TEMPLATE_PIPELINE) {
|
||||
console.error(
|
||||
'ERROR: Cannot run this test target without: --//packages/compiler:use_template_pipeline');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
describe('Signal component DOM property interpolations', () => {
|
||||
it('should bind interpolated values', () => {
|
||||
@Component({
|
||||
signals: true,
|
||||
template: `<div title="Hello, {{name()}}!"></div>`,
|
||||
standalone: true,
|
||||
})
|
||||
class App {
|
||||
name = signal('Angular');
|
||||
}
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const div = fixture.nativeElement.firstChild;
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(div.title).toBe('Hello, Angular!');
|
||||
});
|
||||
|
||||
it('should support updating values', () => {
|
||||
@Component({
|
||||
signals: true,
|
||||
template: `<div title="Hello, {{name()}}!"></div>`,
|
||||
standalone: true,
|
||||
})
|
||||
class App {
|
||||
name = signal('Angular');
|
||||
}
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const div = fixture.nativeElement.firstChild;
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(div.title).toBe('Hello, Angular!');
|
||||
|
||||
fixture.componentInstance.name.set('New Name');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(div.title).toBe('Hello, New Name!');
|
||||
});
|
||||
});
|
||||
@@ -12,7 +12,6 @@ import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {Input} from '../../../src/metadata';
|
||||
|
||||
|
||||
if (!USE_TEMPLATE_PIPELINE) {
|
||||
console.error(
|
||||
'ERROR: Cannot run this test target without: --//packages/compiler:use_template_pipeline');
|
||||
@@ -30,11 +29,10 @@ describe('Signal component input interpolations', () => {
|
||||
@Input() text = input('');
|
||||
}
|
||||
|
||||
// TODO: compiler needs to generate the ɵɵpropertyInterpolationCreate instruction
|
||||
xit('should bind interpolated values', () => {
|
||||
it('should bind interpolated values', () => {
|
||||
@Component({
|
||||
signals: true,
|
||||
template: `<print text="Hello, {{name}}!">`,
|
||||
template: `<print text="Hello, {{name()}}!">`,
|
||||
imports: [Print],
|
||||
standalone: true,
|
||||
})
|
||||
@@ -47,4 +45,26 @@ describe('Signal component input interpolations', () => {
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('Hello, Angular!');
|
||||
});
|
||||
|
||||
it('should allow updating interpolated values', () => {
|
||||
@Component({
|
||||
signals: true,
|
||||
template: `<print text="Hello, {{name()}}!">`,
|
||||
imports: [Print],
|
||||
standalone: true,
|
||||
})
|
||||
class App {
|
||||
name = signal('Angular');
|
||||
}
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('Hello, Angular!');
|
||||
|
||||
fixture.componentInstance.name.set('New Name');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('Hello, New Name!');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, input, Input} from '@angular/core';
|
||||
import {Component, input, Input, signal} from '@angular/core';
|
||||
import {bootstrapApplication} from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'greet',
|
||||
standalone: true,
|
||||
signals: true,
|
||||
template: `{{ counter() }}`,
|
||||
template: `{{ counter() }} -- {{label()}}`,
|
||||
})
|
||||
export class Greet<T> {
|
||||
counter = input(0);
|
||||
@@ -24,6 +24,8 @@ export class Greet<T> {
|
||||
gen = input<T>({required: true});
|
||||
gen2 = input<T>();
|
||||
|
||||
label = input<string>();
|
||||
|
||||
works(): T {
|
||||
return this.gen();
|
||||
}
|
||||
@@ -37,16 +39,22 @@ export class Greet<T> {
|
||||
selector: 'my-app',
|
||||
template: `
|
||||
Hello <greet [counter]="3" [bla4Public]="10" #ok
|
||||
[bla3]="10" [gen]="{yes: true}"
|
||||
[bla3]="someVar" [gen]="{yes: true}" label="Hello {{name()}}"
|
||||
/>
|
||||
|
||||
<button (click)="ok.works().yes">Click</button>
|
||||
<button (click)="updateName()">Change name</button>
|
||||
`,
|
||||
imports: [Greet],
|
||||
signals: true,
|
||||
})
|
||||
export class MyApp {
|
||||
name = signal('Angular');
|
||||
someVar = -10;
|
||||
|
||||
protected updateName() {
|
||||
this.name.update(n => `${n}-`);
|
||||
}
|
||||
}
|
||||
|
||||
bootstrapApplication(MyApp).catch((e) => console.error(e));
|
||||
|
||||
Reference in New Issue
Block a user