我们什么时候应该在 Angular 中使用
signal()
和 model()
?
Angular 表示以下内容:
ModelSignal 是一个 WritableSignal,这意味着可以使用 set 和 update 方法从任何地方更改它的值。当分配新值时,ModelSignal 将发送到其输出。这与InputSignal不同,InputSignal是只读的,只能通过模板更改。
看起来
ModelSignal
和WriteableSignal
一样,唯一的区别是每当写入它时它都会发出change事件?但是当我们使用 WriteableSignal
或 WriteableSignal.set()
时,WriteableSignal.update()
不也会发出更改事件吗?
下面我添加了两个例子。第一个示例使用
signal()
,而第二个示例使用 model()
。两者似乎都有效。对于我的情况,哪个最适合使用?
使用
signal()
的示例:
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
@Component({
selector: 'app-root',
standalone: true,
imports: [FormsModule],
template: `
<input type="text"
[(ngModel)]="quantity"/>
{{quantity()}}
`,
})
export class App {
name = 'Angular';
quantity = signal<number>(1);
}
bootstrapApplication(App);
使用
model()
的示例:
import { Component, model } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
@Component({
selector: 'app-root',
standalone: true,
imports: [FormsModule],
template: `
<input type="text"
[(ngModel)]="quantity"/>
{{quantity()}}
`,
})
export class App {
name = 'Angular';
quantity = model<number>(1);
}
bootstrapApplication(App);
答案在 Angular 文档中给出。
input() 和 model() 函数都是在 Angular 中定义基于信号的输入的方法,但它们在一些方面有所不同:
- model() 定义输入和输出。输出的名称始终是带有 Change 后缀的输入名称,以支持双向绑定。您的指令的使用者将决定他们是否想仅使用输入、仅使用输出或两者都使用。
- ModelSignal 是一个 WritableSignal,这意味着可以使用 set 和 update 方法从任何地方更改它的值。当分配新值时,ModelSignal 将发送到其输出。这与InputSignal不同,InputSignal是只读的,只能通过模板更改。
- 模型输入不支持输入转换,而信号输入则支持。
现在对于信号,我觉得最好的用例是除了父子通信(
@Input
)之外的任何场景,但信号也可以与ngModel
一起使用,但请注意它们没有 signalChange 类型的功能models
有
最好仔细阅读下面的工作示例,其中包含注释,以便您更好地理解差异并深入了解信号!
我已尽力通过以下工作示例以及上述三点来强调我对信号的充分理解!
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { ChildComponent } from './app/child/child.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [ChildComponent],
template: `
<!-- model has @Input and @Output like syntax in once, notice the suffix Change added to the model's name (here its "modelName") -->
<!-- inputs are great for @Input and better because you can make them mandatory, they cannot be changed, as far as I know!
-->
<app-child
[modelName]="name"
(modelNameChange)="updateModelName($event)"
[inputNameWithTransformAlias]="inputNameWithTransform"
[inputNameWithoutTransformAlias]="inputNameWithoutTransform"
[inputNameButMandatoryAlias]="inputNameButMandatory"
/>
<hr/>
`,
})
export class App {
name: string = 'Angular';
inputNameWithTransform: string = 'input';
inputNameWithoutTransform: string = 'input';
inputNameButMandatory: string = 'input but mandatory';
updateModelName(event: string | undefined) {
this.name = event || '';
}
}
bootstrapApplication(App);
import { Component, input, model, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-child',
standalone: true,
imports: [FormsModule],
template: `
<h1>Model Explanation</h1>
<input [(ngModel)]="modelName"/> <!-- use model when you have an ngModel, it works great with it -->
<button (click)="getModel()">get model value</button>
<button (click)="setModel()">set model value</button>
<div>Model Name: {{modelName()}}</div>
<hr/>
<h1>Input Explanation</h1>
<div>Input Name With Transform: {{inputNameWithTransform()}}</div>
<div>Input Name Without Transform: {{inputNameWithoutTransform()}}</div>
<button (click)="getInputs()">get inputs values</button>
<button (click)="setInputs()">set inputs values</button>
<h1>Signal Explanation</h1>
<div>Signal Name: {{signalName()}}</div>
<button (click)="getSignal()">get signal value</button>
<button (click)="setSignal()">set signal value</button>
`,
})
export class ChildComponent {
modelName = model<string>('', { alias: 'modelName' }); // notice there is no transform
inputNameWithTransform = input<string, string>('', {
// notice there is transform when type is <string, string>
alias: 'inputNameWithTransformAlias',
transform: (value: string) => `transformed ${value}`,
});
inputNameWithoutTransform = input<string>('', {
// notice there is no transform when type is <string>
alias: 'inputNameWithoutTransformAlias',
});
inputNameButMandatory = input.required<string, string>({
// no default value, but if not specified on parent will error out!
// notice there is transform when type is <string, string>
alias: 'inputNameButMandatoryAlias',
transform: (value: string) => `transformed ${value}`,
});
signalName = signal<string>('signalName', {
// can set an initial value
// signal do not have alias or transform, good for local variable like functionality!
equal: (a, b) => a === b, // we can write a function which tells us when the signal detects a change,
});
constructor() {}
// model has both get and set methods
setModel() {
this.modelName.set('was set using button');
}
getModel() {
alert(this.modelName());
}
// input has get but no set methods
setInputs() {
alert('check the commented code!');
// uncomment to see the errors!
// this.inputNameWithTransform.set('was set using button');
// this.inputNameWithoutTransform.set('was set using button');
// this.inputNameButMandatory.set('was set using button');
}
getInputs() {
alert(`
inputNameWithTransform: ${this.inputNameWithTransform()}
inputNameWithoutTransform: ${this.inputNameWithoutTransform()}
inputNameButMandatory: ${this.inputNameButMandatory()}
`);
}
// signals has both get and set methods
setSignal() {
this.signalName.set('was set using button');
}
getSignal() {
alert(this.signalName());
}
}