angular 21 升级使用 signals 方案笔记
<h1><a href="https://zodream.cn/blog/0_0/tag/angular.html" title="angular">angular</a> 21 升级使用 signals 方案笔记</h1> <p><code>signals</code> 提供新的数据绑定和变更检测机制。从 <code>angular 17</code> 就开始引入了,</p> <p>优点:</p> <ol> <li>更精细的响应检测。</li> </ol> <p>缺点:</p> <ol> <li>变化太大,</li> </ol> <h2>signal</h2> <p>基本的绑定,可读可写。</p> <p><code>angular 17</code> 之前:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public text = 'hi'; public tap() { this.text = 'hello'; } }</code></pre> <p><code>angular 17</code> 之后:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text() }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public readonly text = signal&lt;string&gt;('hi'); public tap() { this.text.set('hello'); this.text.update(v =&gt; { return 'hello'; }); } }</code></pre> <h3>注意</h3> <ol> <li>使用 <code>set</code> 或 <code>update</code> 更新值。</li> <li>当值是对象时,使用 <code>update</code> 更新某个属性,必须生成新的对象,才能有效检测<pre><code class="language-ts"> public readonly data = signal({ a: 1, b: 2 }); // 错误示范 this.data.update(v =&gt; { v.a = 3 return v; });</code></pre> </li> </ol> <p>// 正确使用 this.data.update(v =&gt; { v.a = 3; return {...v}; // 或者 return {...v, a: 3}; });</p> <pre><code>3. 当值时数组时,使用 `update` 新增或删除某一项 ```ts public readonly items = signal&lt;number[]&gt;([]); // 错误示范 this.items.update(v =&gt; { v.push(1); v.pop(); return v; }); // 正确使用 this.items.update(v =&gt; { v.push(1); return [...v]; // 或者 return [...v, 1]; });</code></pre> <h2>input、output、model</h2> <p><code>input</code> 相当于 <code>@Input()</code>, 但是可读不可写,增加了自定义转换</p> <p><code>output</code> 相当于 <code>@Output()</code>,</p> <p><code>model</code> 相对于 <code>@Input()</code> + <code>@Output()</code>,可读可写</p> <p><code>angular 17</code> 之前:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { @Input() public text = 'hi'; @Output() public textChange = new EventEmitter(); }</code></pre> <p><code>angular 17</code> 之后:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text() }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public readonly text = input('hi', {tranform: parseInt}); public readonly textChange = output(); public readonly text = model(''); }</code></pre> <h2>form</h2> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `&lt;input type="number" [field]="form.a"&gt;`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public readonly form = form(sinal({ a: 1 }), schemaPath =&gt; { required(schemaPath.a); }) } </code></pre> <p>使用 <code>[field]</code> 精选表单绑定,不能自定义 <code>name</code> 属性,</p> <h3>注意</h3> <ol> <li>表单 <code>name</code> 属性自动生成 类似于 <code>[项目名].form1.a</code>,如果介意 <code>name</code>,则推荐使用 原本 <code>FormBuilder</code> 方式</li> <li>一些值限制的变化, <code>select</code> 的值为 <code>string</code>,<code>input type=checkbox</code> 为 <code>boolean</code>, <code>type=number</code> 为 <code>number</code>, 其他则必须为 <code>string</code></li> </ol> <h2>effect</h2> <p>检测 值的变化,替代 <code>ngOnChanges</code></p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text() }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public readonly text = model(''); constructor() { let previousText = ''; effect(() =&gt; { this.text(); // 当 text 发生变化时,TODO previousText = this.text(); // 使用此方法实现值的前后变化检测 }); } }</code></pre> <h2>computed</h2> <p>关联变化,提供值变化前后对比</p> <p><code>angular 17</code> 之前:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text }} {{ twoText }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public text = 'hi'; public get twoText() { return this.text + ', two'; } public tap() { this.text = 'hello'; } }</code></pre> <p><code>angular 17</code> 之后:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text() }} {{ twoText() }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public readonly text = signal&lt;string&gt;('hi'); public readonly twoText = computed(() =&gt; { return this.text() + ', two'; }); public tap() { this.text.set('hello'); this.text.update(v =&gt; { return 'hello'; }); } }</code></pre> <h2>与 signals 使用时失效的旧方法</h2> <p><code>@HostBinding</code> 无法与 <code>signal</code> 等使用</p> <pre><code class="language-ts">@HostBinding('class.open') // 无效 public readonly toggle = model(false);</code></pre> <p>可以使用 <code>effect</code> 同步或</p> <pre><code class="language-ts"> @Component({ standalone: false, selector: 'app-example', template: ``, styleUrls: ['./example.component.scss'], host: { '[class.open]': 'toggle()' } }) export class ExampleComponent { public readonly toggle = model(false); }</code></pre>
