<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Angular表单的演进与最佳实践 on 雪狼的书斋</title>
    <link>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/</link>
    <description>Recent content in Angular表单的演进与最佳实践 on 雪狼的书斋</description>
    <generator>Hugo</generator>
    <language>zh-hans</language>
    <atom:link href="/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>1.Angular表单“变形记”：从“模板驱动”到“响应式”的华丽转身！</title>
      <link>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/010-angular%E8%A1%A8%E5%8D%95%E5%8F%98%E5%BD%A2%E8%AE%B0%E4%BB%8E%E6%A8%A1%E6%9D%BF%E9%A9%B1%E5%8A%A8%E5%88%B0%E5%93%8D%E5%BA%94%E5%BC%8F%E7%9A%84%E5%8D%8E%E4%B8%BD%E8%BD%AC%E8%BA%AB/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/010-angular%E8%A1%A8%E5%8D%95%E5%8F%98%E5%BD%A2%E8%AE%B0%E4%BB%8E%E6%A8%A1%E6%9D%BF%E9%A9%B1%E5%8A%A8%E5%88%B0%E5%93%8D%E5%BA%94%E5%BC%8F%E7%9A%84%E5%8D%8E%E4%B8%BD%E8%BD%AC%E8%BA%AB/</guid>
      <description>&lt;p&gt;嘿，朋友们！我是「雪狼」，一个在前端江湖摸爬滚打多年的老兵。今天，我想和大家聊聊 Angular 表单的「变形记」。在前端开发这场「造物」之旅中，表单（Form）无疑是我们与用户进行信息「握手」的最核心、最频繁的场景。为了能让我们优雅地处理这每一次「握手」，Angular 为我们提供了两套风格迥异、各有所长的「武功秘籍」：&lt;strong&gt;模板驱动表单 (Template-driven Forms)&lt;/strong&gt; 和 &lt;strong&gt;响应式表单 (Reactive Forms)&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;这两者，并非简单的「新旧」之别，而是一场关于「控制权」和「思想」的「变形记」。理解这场「变形」的意义，是精通 Angular 表单开发的第一步。&lt;/p&gt;&#xA;&lt;h2 id=&#34;模板驱动表单所见即所得的快餐哲学&#34;&gt;模板驱动表单：「所见即所得」的快餐哲学&lt;a class=&#34;anchor&#34; href=&#34;#%e6%a8%a1%e6%9d%bf%e9%a9%b1%e5%8a%a8%e8%a1%a8%e5%8d%95%e6%89%80%e8%a7%81%e5%8d%b3%e6%89%80%e5%be%97%e7%9a%84%e5%bf%ab%e9%a4%90%e5%93%b2%e5%ad%a6&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;想象一下，你要组装一台最基础的「代步车」。最快的方式，是直接买一个现成的底盘，然后在上面加装方向盘、座椅等少数几个零件。&lt;/p&gt;&#xA;&lt;p&gt;模板驱动表单，就是这种「快餐哲学」的体现。它的核心思想是： &lt;strong&gt;「真理之源」在模板（HTML）中&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;你通过在模板中添加 &lt;code&gt;ngModel&lt;/code&gt;、&lt;code&gt;ngForm&lt;/code&gt; 等指令，以一种声明式的方式，告诉 Angular：「嘿，这里有一个输入框，你帮我管起来。」 Angular 会在背后「发现」这些指令，并为你自动创建好表单控件的实例。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;快餐店点餐（代码示例）：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;loginForm&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ngForm&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ngSubmit&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;)=&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;login&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;loginForm&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;)&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;username&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;[(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ngModel&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;)]=&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;[(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ngModel&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;)]=&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;submit&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;disabled&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;]=&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;loginForm&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;invalid&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#34;&lt;/span&gt;&amp;gt;登录&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// component.ts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LoginComponent&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;login&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;formValue&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;formValue&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;对于非常简单的表单（如登录、订阅），代码非常简洁，几乎都在 HTML 中完成。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;上手快，心智负担小，真正做到了「所见即所得」。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;当表单逻辑变复杂时（如动态增删、跨字段校验），模板会变得臃肿不堪，逻辑分散，难以维护。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;由于表单模型是 Angular 在背后隐式创建的，进行单元测试变得非常困难。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;响应式表单运筹帷幄的工程师艺术&#34;&gt;响应式表单：「运筹帷幄」的工程师艺术&lt;a class=&#34;anchor&#34; href=&#34;#%e5%93%8d%e5%ba%94%e5%bc%8f%e8%a1%a8%e5%8d%95%e8%bf%90%e7%ad%b9%e5%b8%b7%e5%b9%84%e7%9a%84%e5%b7%a5%e7%a8%8b%e5%b8%88%e8%89%ba%e6%9c%af&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;现在，你不再满足于「代步车」，你想要打造一辆拥有复杂仪表盘、可随时改装引擎的「高性能赛车」。这时，你就需要「响应式」这套「工程师的艺术」了。&lt;/p&gt;&#xA;&lt;p&gt;它的核心思想恰恰相反： &lt;strong&gt;「真理之源」在组件类（TypeScript）中&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;你不再依赖模板，而是在组件类中，通过 &lt;code&gt;FormBuilder&lt;/code&gt;, &lt;code&gt;FormGroup&lt;/code&gt;, &lt;code&gt;FormControl&lt;/code&gt; 等「精密零件」，以编程的方式，&lt;strong&gt;主动、明确地&lt;/strong&gt;构建出整个表单的数据模型和校验规则。模板，仅仅是这个强大模型的「可视化界面」。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;angular_forms_images/source_of_truth.jpg&#34; alt=&#34;文生图：一个对比信息图。左边“模板驱动”，大脑的图标在HTML模板一侧，箭头从模板指向TS类。右边“响应式”，大脑的图标在TS类一侧，箭头从TS类指向模板。直观地展示了“真理之源”的不同。&#34; /&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;赛车改装间（代码示例）：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// component.ts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LoginComponent&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fb&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;inject&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;FormBuilder&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;loginForm&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;group&lt;/span&gt;({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;, [&lt;span style=&#34;color:#a6e22e&#34;&gt;Validators&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Validators&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;minLength&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)]],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Validators&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;login() {&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;loginForm&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;formGroup&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;]=&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;loginForm&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ngSubmit&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;)=&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;login&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;()&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;formControlName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;username&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;formControlName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;submit&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;disabled&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;]=&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;loginForm&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;invalid&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#34;&lt;/span&gt;&amp;gt;登录&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;</description>
    </item>
    <item>
      <title>2.Reactive Forms：Angular中“掌控一切”的表单艺术</title>
      <link>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/020-reactive-formsangular%E4%B8%AD%E6%8E%8C%E6%8E%A7%E4%B8%80%E5%88%87%E7%9A%84%E8%A1%A8%E5%8D%95%E8%89%BA%E6%9C%AF/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/020-reactive-formsangular%E4%B8%AD%E6%8E%8C%E6%8E%A7%E4%B8%80%E5%88%87%E7%9A%84%E8%A1%A8%E5%8D%95%E8%89%BA%E6%9C%AF/</guid>
      <description>&lt;p&gt;如果说模板驱动表单是一位随性的「街头艺人」，能快速地为你画出一幅生动的「简笔画」；那么，响应式表单（Reactive Forms）就是一位严谨的「古典主义画家」，他手握精密的工具，在画室中运筹帷幄，最终创作出一幅结构复杂、层次丰富、光影分明的「油画巨作」。&lt;/p&gt;&#xA;&lt;p&gt;模板驱动追求的是「便捷」，而响应式表单追求的，是「&lt;strong&gt;掌控&lt;/strong&gt;」 。这是一种将用户输入这一最不可预测的「混沌」，完全纳入你代码掌控之下的艺术。&lt;/p&gt;&#xA;&lt;h2 id=&#34;三位一体响应式表单的原子构造&#34;&gt;「三位一体」：响应式表单的「原子」构造&lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%89%e4%bd%8d%e4%b8%80%e4%bd%93%e5%93%8d%e5%ba%94%e5%bc%8f%e8%a1%a8%e5%8d%95%e7%9a%84%e5%8e%9f%e5%ad%90%e6%9e%84%e9%80%a0&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;要理解响应式表单，首先要认识构成它的三个「基本粒子」：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;FormControl&lt;/code&gt;：「原子」&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;它代表一个单独的、最基础的输入单元，比如一个 &lt;code&gt;input&lt;/code&gt;、一个 &lt;code&gt;textarea&lt;/code&gt; 或一个 &lt;code&gt;select&lt;/code&gt;。它独自追踪着自己的&lt;strong&gt;值（value）&lt;/strong&gt;、&lt;strong&gt;校验状态（valid/invalid）&lt;strong&gt;以及&lt;/strong&gt;用户交互状态（touched/dirty）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;FormGroup&lt;/code&gt;：「分子」&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;它是一个「容器」，将多个 &lt;code&gt;FormControl&lt;/code&gt; 或其他 &lt;code&gt;FormGroup&lt;/code&gt; 组织在一起，形成一个有结构的数据对象。一个注册表单，就是一个包含了 &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt; 等多个 &lt;code&gt;FormControl&lt;/code&gt; 的 &lt;code&gt;FormGroup&lt;/code&gt;。它会自动聚合其所有子控件的值和状态。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;FormArray&lt;/code&gt;：「链条」&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;它是一个「动态数组」，用于管理一个长度可变的控件列表。当你需要用户「添加另一个&amp;hellip;」时（比如添加多项技能、多个收货地址），&lt;code&gt;FormArray&lt;/code&gt; 就是你的不二之选。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;formbuilder艺术家的调色盘&#34;&gt;&lt;code&gt;FormBuilder&lt;/code&gt;：艺术家的「调色盘」&lt;a class=&#34;anchor&#34; href=&#34;#formbuilder%e8%89%ba%e6%9c%af%e5%ae%b6%e7%9a%84%e8%b0%83%e8%89%b2%e7%9b%98&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;虽然你可以通过 &lt;code&gt;new FormGroup({ ... })&lt;/code&gt; 的方式来手动创建表单模型，但 Angular 提供了一个更便捷的「艺术家调色盘」 —— &lt;code&gt;FormBuilder&lt;/code&gt; 服务。它能让你用更简洁的语法来「调制」你的表单。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;未使用 &lt;code&gt;FormBuilder&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;loginForm&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FormGroup&lt;/span&gt;({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FormControl&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;address&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FormGroup&lt;/span&gt;({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;street&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FormControl&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;city&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FormControl&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  })&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;使用 &lt;code&gt;FormBuilder&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;inject&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;FormBuilder&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Validators&lt;/span&gt; } &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;@angular/forms&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fb&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;inject&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;FormBuilder&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;loginForm&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;group&lt;/span&gt;({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 数组语法：[默认值, 同步校验器, 异步校验器]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Validators&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;address&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;this.fb.group&lt;/span&gt;({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;street&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;city&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  })&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;第六感通过-valuechanges-和-statuschanges-响应变化&#34;&gt;「第六感」：通过 &lt;code&gt;valueChanges&lt;/code&gt; 和 &lt;code&gt;statusChanges&lt;/code&gt; 响应变化&lt;a class=&#34;anchor&#34; href=&#34;#%e7%ac%ac%e5%85%ad%e6%84%9f%e9%80%9a%e8%bf%87-valuechanges-%e5%92%8c-statuschanges-%e5%93%8d%e5%ba%94%e5%8f%98%e5%8c%96&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;这才是「响应式」三个字的精髓所在。每一个表单控件（&lt;code&gt;FormControl&lt;/code&gt;, &lt;code&gt;FormGroup&lt;/code&gt;, &lt;code&gt;FormArray&lt;/code&gt;），都自带两个强大的 &lt;code&gt;Observable&lt;/code&gt; 属性，让你能像拥有「第六感」一样，实时感知表单的任何风吹草动。&lt;/p&gt;</description>
    </item>
    <item>
      <title>3.Template-driven Forms：Angular中“所见即所得”的表单哲学</title>
      <link>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/030-template-driven-formsangular%E4%B8%AD%E6%89%80%E8%A7%81%E5%8D%B3%E6%89%80%E5%BE%97%E7%9A%84%E8%A1%A8%E5%8D%95%E5%93%B2%E5%AD%A6/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/030-template-driven-formsangular%E4%B8%AD%E6%89%80%E8%A7%81%E5%8D%B3%E6%89%80%E5%BE%97%E7%9A%84%E8%A1%A8%E5%8D%95%E5%93%B2%E5%AD%A6/</guid>
      <description>&lt;p&gt;在上一篇，我们领略了响应式表单那「掌控一切」的工程师艺术。它精密、强大、可测试，是打造「高性能赛车」的不二之选。但，我们是否每次出行，都需要一辆 F1赛车呢？&lt;/p&gt;&#xA;&lt;p&gt;有时候，你只是想去街角的便利店买瓶酱油。此时，一辆轻便、灵活的「小摩托」，或许比那台需要预热、操作复杂的赛车，来得更惬意、更高效。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;模板驱动表单（Template-driven Forms）&lt;/strong&gt;，就是 Angular 为我们准备的这辆「小摩托」。它不追求极致的控制，而是信奉一种简单、直观的「所见即所得」（WYSIWYG）哲学。&lt;/p&gt;&#xA;&lt;h2 id=&#34;哲学的核心真理在模板&#34;&gt;哲学的核心：「真理」在模板&lt;a class=&#34;anchor&#34; href=&#34;#%e5%93%b2%e5%ad%a6%e7%9a%84%e6%a0%b8%e5%bf%83%e7%9c%9f%e7%90%86%e5%9c%a8%e6%a8%a1%e6%9d%bf&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;与响应式表单截然相反，模板驱动表单的「真理之源」，存在于 &lt;strong&gt;HTML 模板&lt;/strong&gt;之中。&lt;/p&gt;&#xA;&lt;p&gt;你无需在组件类中去构建任何 &lt;code&gt;FormGroup&lt;/code&gt; 或 &lt;code&gt;FormControl&lt;/code&gt;。你只需像写普通 HTML 一样，在模板中放置你的表单元素，然后通过添加一些特殊的「魔法指令」，Angular 就会在幕后，默默地为你「发现」并构建起一整套表单模型。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;angular_forms_images/source_of_truth.jpg&#34; alt=&#34;文生图：一个对比信息图。左边“模板驱动”，大脑的图标在HTML模板一侧，箭头从模板指向TS类。右边“响应式”，大脑的图标在TS类一侧，箭头从TS类指向模板。直观地展示了“真理之源”的不同。&#34; /&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;魔法的来源ngform-ngmodel-与-ngmodel&#34;&gt;「魔法」的来源：&lt;code&gt;ngForm&lt;/code&gt;, &lt;code&gt;ngModel&lt;/code&gt; 与 &lt;code&gt;[(ngModel)]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#%e9%ad%94%e6%b3%95%e7%9a%84%e6%9d%a5%e6%ba%90ngform-ngmodel-%e4%b8%8e-ngmodel&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;这套魔法，由几位关键的「魔术师」（指令）共同完成。&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;ngForm&lt;/code&gt;&lt;/strong&gt;：当你将这个指令应用在一个 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 标签上时，它会自动地、隐式地为你创建一个 &lt;code&gt;FormGroup&lt;/code&gt; 实例来包裹整个表单。你可以通过一个模板引用变量（如 &lt;code&gt;#myForm=&amp;quot;ngForm&amp;quot;&lt;/code&gt;）来获取这个实例的引用，从而在模板中访问表单的整体状态（如 &lt;code&gt;myForm.valid&lt;/code&gt;, &lt;code&gt;myForm.value&lt;/code&gt;）。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;ngModel&lt;/code&gt;&lt;/strong&gt;：当你在一个带有 &lt;code&gt;name&lt;/code&gt; 属性的表单元素（如 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;）上使用 &lt;code&gt;ngModel&lt;/code&gt; 指令时，它会自动为这个元素创建一个 &lt;code&gt;FormControl&lt;/code&gt; 实例，并将其注册到父级的 &lt;code&gt;ngForm&lt;/code&gt; 所创建的 &lt;code&gt;FormGroup&lt;/code&gt; 中。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;[(ngModel)]&lt;/code&gt;&lt;/strong&gt;：「香蕉套盒子」语法，这是双向绑定的精髓。它实际上是两个指令的语法糖：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;[ngModel]&lt;/code&gt;：属性绑定。将组件类中的属性值，单向地传递给表单元素。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;(ngModelChange)&lt;/code&gt;：事件绑定。当表单元素的值发生变化时，将新值发射出去，更新到组件类的属性上。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;code&gt;[(ngModel)]&lt;/code&gt; 将这两者合二为一，实现了视图与模型之间的自动同步。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;小摩托实战一个简单的订阅表单&#34;&gt;「小摩托」实战：一个简单的订阅表单&lt;a class=&#34;anchor&#34; href=&#34;#%e5%b0%8f%e6%91%a9%e6%89%98%e5%ae%9e%e6%88%98%e4%b8%80%e4%b8%aa%e7%ae%80%e5%8d%95%e7%9a%84%e8%ae%a2%e9%98%85%e8%a1%a8%e5%8d%95&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;让我们用模板驱动的方式，来构建一个邮件订阅表单。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;模板代码 (&lt;code&gt;.html&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;signupForm&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ngForm&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ngSubmit&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;)=&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;subscribe&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;()&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;h3&lt;/span&gt;&amp;gt;订阅我们的资讯！&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;h3&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;placeholder&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;请输入您的邮箱&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;[(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ngModel&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;)]=&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;emailAddress&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;emailInput&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ngModel&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  @if (emailInput.invalid &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; (emailInput.dirty || emailInput.touched)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;error-feedback&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      @if (emailInput.errors?.[&amp;#39;required&amp;#39;]) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;邮箱不能为空。&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      @if (emailInput.errors?.[&amp;#39;email&amp;#39;]) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;请输入一个有效的邮箱地址。&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;submit&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;disabled&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;]=&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;signupForm&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;invalid&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#34;&lt;/span&gt;&amp;gt;订阅&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;组件代码 (&lt;code&gt;.ts&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>4.表单验证：Angular中让你的数据“滴水不漏”的秘密武器</title>
      <link>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/040-%E8%A1%A8%E5%8D%95%E9%AA%8C%E8%AF%81angular%E4%B8%AD%E8%AE%A9%E4%BD%A0%E7%9A%84%E6%95%B0%E6%8D%AE%E6%BB%B4%E6%B0%B4%E4%B8%8D%E6%BC%8F%E7%9A%84%E7%A7%98%E5%AF%86%E6%AD%A6%E5%99%A8/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/040-%E8%A1%A8%E5%8D%95%E9%AA%8C%E8%AF%81angular%E4%B8%AD%E8%AE%A9%E4%BD%A0%E7%9A%84%E6%95%B0%E6%8D%AE%E6%BB%B4%E6%B0%B4%E4%B8%8D%E6%BC%8F%E7%9A%84%E7%A7%98%E5%AF%86%E6%AD%A6%E5%99%A8/</guid>
      <description>&lt;p&gt;一个没有验证的表单，就像一座不设防的城池，任何人、任何「数据」都可以长驱直入。其结果，轻则让你的应用状态陷入混乱，重则导致脏数据入库，引发更深层次的灾难。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;表单验证，就是你应用数据的「护城河」与「城墙」&lt;/strong&gt;。它是你作为一名严谨的工程师，为保障数据质量、提升用户体验而必须掌握的「秘密武器」。幸运的是，Angular 为我们提供了 arsenal（一整套）强大而灵活的验证「兵器谱」。&lt;/p&gt;&#xA;&lt;h2 id=&#34;两大阵营不同的练兵方式&#34;&gt;两大阵营：不同的「练兵」方式&lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%a4%e5%a4%a7%e9%98%b5%e8%90%a5%e4%b8%8d%e5%90%8c%e7%9a%84%e7%bb%83%e5%85%b5%e6%96%b9%e5%bc%8f&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;在 Angular 中，应用验证规则的方式，根据你选择的表单类型而有所不同：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;响应式表单&lt;/strong&gt;：验证器是&lt;strong&gt;函数&lt;/strong&gt;。你在组件的 TS 文件中，将这些函数直接传递给 &lt;code&gt;FormControl&lt;/code&gt; 的构造器。逻辑与模型在代码中紧密结合。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;模板驱动表单&lt;/strong&gt;：验证器是&lt;strong&gt;指令&lt;/strong&gt;。你将 &lt;code&gt;required&lt;/code&gt;, &lt;code&gt;minLength&lt;/code&gt; 等指令直接应用在模板的 HTML 元素上。逻辑体现在视图中。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;虽然应用方式不同，但验证器本身的核心（无论是内置的还是自定义的）是可以通用的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;兵器谱之内置验证器&#34;&gt;「兵器谱」之：内置验证器&lt;a class=&#34;anchor&#34; href=&#34;#%e5%85%b5%e5%99%a8%e8%b0%b1%e4%b9%8b%e5%86%85%e7%bd%ae%e9%aa%8c%e8%af%81%e5%99%a8&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;@angular/forms&lt;/code&gt; 中的 &lt;code&gt;Validators&lt;/code&gt; 类，为我们提供了最常用的一批「制式兵器」，开箱即用。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;Validators.required&lt;/code&gt;：必填项。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;Validators.minLength(number)&lt;/code&gt;：最小长度。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;Validators.maxLength(number)&lt;/code&gt;：最大长度。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;Validators.pattern(regex)&lt;/code&gt;：必须匹配正则表达式。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;Validators.email&lt;/code&gt;：必须是合法的邮件格式。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;Validators.min(number)&lt;/code&gt;：最小值（用于数字）。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;Validators.max(number)&lt;/code&gt;：最大值（用于数字）。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;如何使用？&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;响应式表单中：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;Validators&lt;/span&gt; } &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;@angular/forms&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;group&lt;/span&gt;({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 将校验器函数数组作为第二个参数传入&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;, [&lt;span style=&#34;color:#a6e22e&#34;&gt;Validators&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Validators&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;minLength&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)]],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;, [&lt;span style=&#34;color:#a6e22e&#34;&gt;Validators&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Validators&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;]]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;模板驱动表单中：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;username&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ngModel&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;minlength&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;3&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ngModel&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;email&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;锻造神兵之一自定义同步验证器&#34;&gt;「锻造神兵」之一：自定义同步验证器&lt;a class=&#34;anchor&#34; href=&#34;#%e9%94%bb%e9%80%a0%e7%a5%9e%e5%85%b5%e4%b9%8b%e4%b8%80%e8%87%aa%e5%ae%9a%e4%b9%89%e5%90%8c%e6%ad%a5%e9%aa%8c%e8%af%81%e5%99%a8&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;当内置兵器不称手时，我们就需要自己「锻造」一柄。比如，我们需要一个不允许输入特殊字符的验证器。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;规则&lt;/strong&gt;：一个同步验证器，就是一个接收 &lt;code&gt;AbstractControl&lt;/code&gt; 作为参数，并在&lt;strong&gt;验证失败时返回一个错误对象 &lt;code&gt;ValidationErrors&lt;/code&gt;&lt;/strong&gt;、&lt;strong&gt;验证成功时返回 &lt;code&gt;null&lt;/code&gt;&lt;/strong&gt; 的函数。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// no-special-chars.validator.ts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;AbstractControl&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ValidationErrors&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ValidatorFn&lt;/span&gt; } &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;@angular/forms&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;noSpecialCharsValidator&lt;/span&gt;()&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ValidatorFn&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;control&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;AbstractControl&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ValidationErrors&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hasSpecialChars&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/[!@#$%^&amp;amp;*(),.?&amp;#34;:{}|&amp;lt;&amp;gt;]/&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;test&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;control&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 如果包含特殊字符，返回一个错误对象，键名代表错误类型&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hasSpecialChars&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;hasSpecialChars&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; } &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;在响应式表单中使用：&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>5.动态表单：Angular中“千变万化”的表单魔法</title>
      <link>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/050-%E5%8A%A8%E6%80%81%E8%A1%A8%E5%8D%95angular%E4%B8%AD%E5%8D%83%E5%8F%98%E4%B8%87%E5%8C%96%E7%9A%84%E8%A1%A8%E5%8D%95%E9%AD%94%E6%B3%95/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/050-%E5%8A%A8%E6%80%81%E8%A1%A8%E5%8D%95angular%E4%B8%AD%E5%8D%83%E5%8F%98%E4%B8%87%E5%8C%96%E7%9A%84%E8%A1%A8%E5%8D%95%E9%AD%94%E6%B3%95/</guid>
      <description>&lt;p&gt;你已经掌握了响应式表单的「基本功」，能用 &lt;code&gt;FormGroup&lt;/code&gt; 和 &lt;code&gt;FormControl&lt;/code&gt; 搭建出结构固定的表单。但如果，你的表单不是「固定」的呢？&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;如果用户需要自己「添加多个」联系电话？&lt;/li&gt;&#xA;&lt;li&gt;如果整个表单的结构，需要根据从服务器拉取的一份 JSON 配置来动态生成？&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;此时，你就需要学习响应式表单的「禁咒」级魔法 —— &lt;strong&gt;动态表单&lt;/strong&gt;。这是一种「无中生有」、「千变万化」的至高艺术，能让你从容应对任何复杂的表单需求。&lt;/p&gt;&#xA;&lt;h2 id=&#34;魔法咒语formarray-addcontrol-removecontrol&#34;&gt;魔法咒语：&lt;code&gt;FormArray&lt;/code&gt;, &lt;code&gt;addControl&lt;/code&gt;, &lt;code&gt;removeControl&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#%e9%ad%94%e6%b3%95%e5%92%92%e8%af%adformarray-addcontrol-removecontrol&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;要施展动态表单的魔法，你需要先认识几句关键「咒语」：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;FormArray&lt;/code&gt;&lt;/strong&gt;：这是你的「魔法口袋」，一个可以容纳动态数量控件的特殊数组。它是实现「动态增删」的基础。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;addControl()&lt;/code&gt; / &lt;code&gt;removeControl()&lt;/code&gt;&lt;/strong&gt;：在 &lt;code&gt;FormGroup&lt;/code&gt; 上施放的「咒语」，允许你在运行时，向一个已存在的表单组中添加或移除一个控件。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;push()&lt;/code&gt; / &lt;code&gt;removeAt()&lt;/code&gt;&lt;/strong&gt;：在 &lt;code&gt;FormArray&lt;/code&gt; 上施放的「咒语」，用于在表单数组的末尾添加一个新控件，或移除指定位置的控件。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;场景一多重影分身之术--动态增删-formarray&#34;&gt;场景一：「多重影分身之术」 —— 动态增删 &lt;code&gt;FormArray&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#%e5%9c%ba%e6%99%af%e4%b8%80%e5%a4%9a%e9%87%8d%e5%bd%b1%e5%88%86%e8%ba%ab%e4%b9%8b%e6%9c%af--%e5%8a%a8%e6%80%81%e5%a2%9e%e5%88%a0-formarray&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;这是动态表单最常见的应用场景。让我们再次请出「简历技能」这个经典例子。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;组件代码：&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// resume.component.ts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ResumeComponent&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fb&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;inject&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;FormBuilder&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;resumeForm&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;group&lt;/span&gt;({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 1. 初始化一个空的 FormArray&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;skills&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;this.fb.array&lt;/span&gt;([])&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 2. 为了方便在模板中使用，创建一个 getter&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;skills() {&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;resumeForm&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;skills&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FormArray&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 3. 施法：添加一个新技能&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;addSkill() {&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// push 一个新的 FormControl 到数组中&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;skills&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;control&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Validators&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt;));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 4. 施法：移除一个技能&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;removeSkill&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;skills&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;removeAt&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;模板代码：&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>6.自定义表单控件：Angular中“独家定制”你的输入体验</title>
      <link>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/060-%E8%87%AA%E5%AE%9A%E4%B9%89%E8%A1%A8%E5%8D%95%E6%8E%A7%E4%BB%B6angular%E4%B8%AD%E7%8B%AC%E5%AE%B6%E5%AE%9A%E5%88%B6%E4%BD%A0%E7%9A%84%E8%BE%93%E5%85%A5%E4%BD%93%E9%AA%8C/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/060-%E8%87%AA%E5%AE%9A%E4%B9%89%E8%A1%A8%E5%8D%95%E6%8E%A7%E4%BB%B6angular%E4%B8%AD%E7%8B%AC%E5%AE%B6%E5%AE%9A%E5%88%B6%E4%BD%A0%E7%9A%84%E8%BE%93%E5%85%A5%E4%BD%93%E9%AA%8C/</guid>
      <description>&lt;p&gt;我们已经习惯了使用 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; 等标准的表单控件。但如果，你的产品经理提出了一个「天马行空」的需求呢？&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;「我想要一个可以拖拽打分的星级评分控件！」&lt;/li&gt;&#xA;&lt;li&gt;「这里需要一个能拾取颜色的调色盘！」&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这时，你会发现，Angular 的内置控件已经不够用了。你需要的，是创造一个属于你自己的、能与 Angular 表单体系（无论是模板驱动还是响应式）无缝协作的&lt;strong&gt;自定义表单控件&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;实现这一「独家定制」魔法的「秘密契约」，就是 &lt;strong&gt;&lt;code&gt;ControlValueAccessor&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;翻译官的使命controlvalueaccessor-是什么&#34;&gt;「翻译官」的使命：&lt;code&gt;ControlValueAccessor&lt;/code&gt; 是什么？&lt;a class=&#34;anchor&#34; href=&#34;#%e7%bf%bb%e8%af%91%e5%ae%98%e7%9a%84%e4%bd%bf%e5%91%bdcontrolvalueaccessor-%e6%98%af%e4%bb%80%e4%b9%88&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;ControlValueAccessor&lt;/code&gt; (简称 CVA) 是一个接口。任何一个组件，只要实现了这个接口，就等于在告诉 Angular：「嘿，我虽然外表特立独行，但我懂得你们表单世界的『官方语言』！你可以像对待普通 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 一样对待我。」&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;比喻一下&lt;/strong&gt;：&lt;code&gt;FormControl&lt;/code&gt; 是一个标准的「PS5游戏手柄」，而你的「星级评分组件」是一个你亲手打造的、独一無二的「高达模型」。&lt;code&gt;ControlValueAccessor&lt;/code&gt; 就是那个「万能转换器」，它负责将手柄的通用信号（「设置值」、「禁用」），翻译成高达能听懂的指令；同时，当用户手动掰动高达的机械臂时（用户交互），它又负责将这个动作，翻译成手柄能理解的「值已改变」的信号，并报告回去。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;angular_forms_images/cva_adapter.jpg&#34; alt=&#34;文生图：一个PS5游戏手柄（FormControl），通过一个发光的、标有“CVA”的适配器，连接到一个复杂的机器人模型上。手柄发出的信号通过适配器转换后，控制着机器人的动作。风格：科技感、概念图解。&#34; /&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;万能转换器的四个接口&#34;&gt;「万能转换器」的四个接口&lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%87%e8%83%bd%e8%bd%ac%e6%8d%a2%e5%99%a8%e7%9a%84%e5%9b%9b%e4%b8%aa%e6%8e%a5%e5%8f%a3&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;要实现这个「转换器」，你需要实现 &lt;code&gt;ControlValueAccessor&lt;/code&gt; 接口中最多四个关键方法。&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;writeValue(obj: any): void&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;方向&lt;/strong&gt;：FormControl -&amp;gt; 你的组件&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：当表单模型从外部被修改时（比如调用了 &lt;code&gt;form.patchValue()&lt;/code&gt;），Angular 会调用此方法，并将新值 &lt;code&gt;obj&lt;/code&gt; 传给你。你的任务，就是接收这个值，并用它来更新你组件的内部 UI。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;翻译&lt;/strong&gt;：「手柄说：目标值是 5。」&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;registerOnChange(fn: any): void&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;方向&lt;/strong&gt;：你的组件 -&amp;gt; FormControl&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：在初始化时，Angular 会传给你一个回调函数 &lt;code&gt;fn&lt;/code&gt;（我们通常叫它 &lt;code&gt;onChange&lt;/code&gt;）。你必须把它&lt;strong&gt;保存&lt;/strong&gt;起来。当你的组件内部因为用户交互而导致值发生变化时，你必须&lt;strong&gt;调用&lt;/strong&gt;这个保存好的 &lt;code&gt;onChange(newValue)&lt;/code&gt; 函数，把新值报告给 &lt;code&gt;FormControl&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;翻译&lt;/strong&gt;：「手柄说：这是我的『报告专线』，有情况随时打给我。」&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;registerOnTouched(fn: any): void&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;方向&lt;/strong&gt;：你的组件 -&amp;gt; FormControl&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：与 &lt;code&gt;registerOnChange&lt;/code&gt; 类似，Angular 会传给你另一个回调函数 &lt;code&gt;fn&lt;/code&gt;（我们通常叫它 &lt;code&gt;onTouched&lt;/code&gt;）。你应该在你的组件被视为「已接触」（通常是在 &lt;code&gt;blur&lt;/code&gt; 事件或首次交互后）时，调用一次这个 &lt;code&gt;onTouched()&lt;/code&gt; 函数。&lt;/p&gt;</description>
    </item>
    <item>
      <title>7.Angular表单的“痛点”与“疗法”：告别冗余，拥抱简洁</title>
      <link>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/070-angular%E8%A1%A8%E5%8D%95%E7%9A%84%E7%97%9B%E7%82%B9%E4%B8%8E%E7%96%97%E6%B3%95%E5%91%8A%E5%88%AB%E5%86%97%E4%BD%99%E6%8B%A5%E6%8A%B1%E7%AE%80%E6%B4%81/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/070-angular%E8%A1%A8%E5%8D%95%E7%9A%84%E7%97%9B%E7%82%B9%E4%B8%8E%E7%96%97%E6%B3%95%E5%91%8A%E5%88%AB%E5%86%97%E4%BD%99%E6%8B%A5%E6%8A%B1%E7%AE%80%E6%B4%81/</guid>
      <description>&lt;p&gt;Angular 的响应式表单功能强大，它为我们提供了「掌控一切」的工具。但在实际开发中，你是否也曾感到一些「痛点」？&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;每个输入框都要写一大段重复的校验信息显示逻辑，模板显得臃肿不堪。&lt;/li&gt;&#xA;&lt;li&gt;复杂的嵌套表单，访问深层控件需要写长长的 &lt;code&gt;.get().get()...&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;希望所有校验错误只在点击「提交」后才显示，而不是用户一离开输入框就立即跳出来。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这些「症状」让你的表单代码显得冗余、复杂，甚至让你觉得有点「不那么 Angular」。别担心，雪狼今天就化身你的「表单疗法师」，为你诊断这些痛点，并提供有效的「疗法」，帮助你的 Angular 表单告别冗余，拥抱简洁与优雅。&lt;/p&gt;&#xA;&lt;h2 id=&#34;痛点一重复的校验信息显示&#34;&gt;痛点一：重复的校验信息显示&lt;a class=&#34;anchor&#34; href=&#34;#%e7%97%9b%e7%82%b9%e4%b8%80%e9%87%8d%e5%a4%8d%e7%9a%84%e6%a0%a1%e9%aa%8c%e4%bf%a1%e6%81%af%e6%98%be%e7%a4%ba&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;症状&lt;/strong&gt;：你的模板中充斥着这样的代码，只为显示一个输入框的错误信息。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- login.component.html --&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;formControlName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;username&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@if (form.get(&amp;#39;username&amp;#39;)?.invalid &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; (form.get(&amp;#39;username&amp;#39;)?.dirty || form.get(&amp;#39;username&amp;#39;)?.touched)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;error-message&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @if (form.get(&amp;#39;username&amp;#39;)?.errors?.[&amp;#39;required&amp;#39;]) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;用户名不能为空。&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @if (form.get(&amp;#39;username&amp;#39;)?.errors?.[&amp;#39;minlength&amp;#39;]) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;用户名至少需要{{ form.get(&amp;#39;username&amp;#39;)?.errors?.[&amp;#39;minlength&amp;#39;].requiredLength }}个字符。&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;span&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这段代码每重复一次，你的心中就多一份焦虑。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;疗法：可复用的错误信息组件&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;将这部分逻辑封装成一个独立的、可复用的组件，是解决冗余的「特效药」。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// app-form-error.component.ts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; {&lt;span style=&#34;color:#a6e22e&#34;&gt;Component&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Input&lt;/span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;@angular/core&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; {&lt;span style=&#34;color:#a6e22e&#34;&gt;AbstractControl&lt;/span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;@angular/forms&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; {&lt;span style=&#34;color:#a6e22e&#34;&gt;CommonModule&lt;/span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;@angular/common&amp;#39;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 或者独立组件中直接用 @if&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;@Component&lt;/span&gt;({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;selector&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;app-form-error&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;standalone&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;imports&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#a6e22e&#34;&gt;CommonModule&lt;/span&gt;], &lt;span style=&#34;color:#75715e&#34;&gt;// if using ngIf/ngFor&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;template&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    @if (control &amp;amp;&amp;amp; control.invalid &amp;amp;&amp;amp; (control.dirty || control.touched)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      &amp;lt;div class=&amp;#34;error-message&amp;#34;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        @if (control.errors?.[&amp;#39;required&amp;#39;]) { &amp;lt;span&amp;gt;此项必填。&amp;lt;/span&amp;gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        @if (control.errors?.[&amp;#39;email&amp;#39;]) { &amp;lt;span&amp;gt;邮箱格式不正确。&amp;lt;/span&amp;gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        @if (control.errors?.[&amp;#39;minlength&amp;#39;]) { &amp;lt;span&amp;gt;至少需要{{ control.errors?.[&amp;#39;minlength&amp;#39;].requiredLength }}个字符。&amp;lt;/span&amp;gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        &amp;lt;!-- 根据需要添加更多错误类型 --&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        @if (control.errors?.[&amp;#39;customError&amp;#39;]) { &amp;lt;span&amp;gt;{{ control.errors?.[&amp;#39;customError&amp;#39;] }}&amp;lt;/span&amp;gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        &amp;lt;!-- 如果是异步校验的 pending 状态 --&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        @if (control.pending) { &amp;lt;span class=&amp;#34;pending-message&amp;#34;&amp;gt;正在验证...&amp;lt;/span&amp;gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      &amp;lt;/div&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  `&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;styles&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;`...`&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AppFormErrorComponent&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 使用 signal input&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;control&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;required&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;AbstractControl&lt;/span&gt;&amp;gt;();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;现在，你的主表单模板变得无比清爽：&lt;/p&gt;</description>
    </item>
    <item>
      <title>8.表单的“禅意”：Angular中构建用户友好的极致体验</title>
      <link>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/080-%E8%A1%A8%E5%8D%95%E7%9A%84%E7%A6%85%E6%84%8Fangular%E4%B8%AD%E6%9E%84%E5%BB%BA%E7%94%A8%E6%88%B7%E5%8F%8B%E5%A5%BD%E7%9A%84%E6%9E%81%E8%87%B4%E4%BD%93%E9%AA%8C/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/angular/angular%E8%A1%A8%E5%8D%95%E7%9A%84%E6%BC%94%E8%BF%9B%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/080-%E8%A1%A8%E5%8D%95%E7%9A%84%E7%A6%85%E6%84%8Fangular%E4%B8%AD%E6%9E%84%E5%BB%BA%E7%94%A8%E6%88%B7%E5%8F%8B%E5%A5%BD%E7%9A%84%E6%9E%81%E8%87%B4%E4%BD%93%E9%AA%8C/</guid>
      <description>&lt;p&gt;我们已经深入探讨了 Angular 表单的「术」（如何构建、验证、动态化、定制化）。然而，技术的终极目的，并非仅仅是实现功能。对于用户体验至上的前端应用而言，表单的终极追求，是一种「&lt;strong&gt;禅意&lt;/strong&gt;」 ：让用户在使用时感到「无感」，甚至「愉悦」。&lt;/p&gt;&#xA;&lt;p&gt;好的表单，就像一阵清风，不着痕迹，却润物无声。用户完成操作后，甚至不会意识到自己刚刚填过一个表单。这才是表单设计的最高境界。&lt;/p&gt;&#xA;&lt;h2 id=&#34;禅意一清晰性--一目了然&#34;&gt;禅意一：清晰性 —— 「一目了然」&lt;a class=&#34;anchor&#34; href=&#34;#%e7%a6%85%e6%84%8f%e4%b8%80%e6%b8%85%e6%99%b0%e6%80%a7--%e4%b8%80%e7%9b%ae%e4%ba%86%e7%84%b6&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;痛点&lt;/strong&gt;：模糊的标签，隐藏的必填项，令人费解的错误信息。用户需要猜测、思考，甚至放弃。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;禅道&lt;/strong&gt;：让表单的意图，像山间的清泉，一目了然。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;标签与输入框&lt;/strong&gt;：为每个输入框提供清晰、简洁的 &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;，并且永远不要用 &lt;code&gt;placeholder&lt;/code&gt; 来替代 &lt;code&gt;label&lt;/code&gt;。&lt;code&gt;placeholder&lt;/code&gt; 应该作为提示信息而存在。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;必填指示&lt;/strong&gt;：明确标示必填项（如 &lt;code&gt;*&lt;/code&gt; 号），让用户在输入前就有所预期。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;错误反馈&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;错误信息应显示在&lt;strong&gt;相关输入框附近&lt;/strong&gt;，而不是在表单顶部。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;错误信息应&lt;strong&gt;具体&lt;/strong&gt;，告诉用户哪里错了，以及&lt;strong&gt;如何修正&lt;/strong&gt;（如「密码至少需要8位，包含大小写字母和数字」）。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;在用户输入时进行&lt;strong&gt;实时校验&lt;/strong&gt;，并在用户「离开」输入框 (&lt;code&gt;blur&lt;/code&gt;) 时显示错误，而不是等到用户点击提交后才全部爆发。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;禅意二高效性--大道至简&#34;&gt;禅意二：高效性 —— 「大道至简」&lt;a class=&#34;anchor&#34; href=&#34;#%e7%a6%85%e6%84%8f%e4%ba%8c%e9%ab%98%e6%95%88%e6%80%a7--%e5%a4%a7%e9%81%93%e8%87%b3%e7%ae%80&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;痛点&lt;/strong&gt;：过多的字段，不必要的步骤，重复的输入。用户感到冗长、繁琐。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;禅道&lt;/strong&gt;：让用户以最少的思考、最少的操作，最快地完成任务。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;极简原则&lt;/strong&gt;：只询问真正必要的信息。每一个额外的字段，都是对用户耐心的一种挑战。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;智能默认值&lt;/strong&gt;：根据用户历史、地理位置等信息，预填充最可能的值，减少用户输入。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;自动补全与建议&lt;/strong&gt;：为地址、姓名等字段提供自动补全功能。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;键盘友好&lt;/strong&gt;：确保所有表单元素都可以通过 &lt;code&gt;Tab&lt;/code&gt; 键轻松导航，&lt;code&gt;Enter&lt;/code&gt; 键能够触发提交。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;多步表单的进度指示&lt;/strong&gt;：对于复杂的多步表单，清晰的进度条能有效缓解用户焦虑。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;禅意三韧性--容错之道&#34;&gt;禅意三：韧性 —— 「容错之道」&lt;a class=&#34;anchor&#34; href=&#34;#%e7%a6%85%e6%84%8f%e4%b8%89%e9%9f%a7%e6%80%a7--%e5%ae%b9%e9%94%99%e4%b9%8b%e9%81%93&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;痛点&lt;/strong&gt;：用户不小心犯错后，得到的是生硬的报错，甚至所有输入都被清空。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;禅道&lt;/strong&gt;：表单应该像一位宽厚的导师，引导用户改正错误，而不是惩罚他们。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;非破坏性验证&lt;/strong&gt;：在显示错误时，不要清除用户已输入的数据。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;提供修正建议&lt;/strong&gt;：当用户输入错误时，除了报错，更要告诉他们「怎么办」。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;自动保存/草稿&lt;/strong&gt;：对于冗长的表单，提供自动保存功能，防止用户意外丢失辛苦输入的内容。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;撤销操作&lt;/strong&gt;：如果可能，为某些操作提供撤销功能。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;清晰的行动召唤&lt;/strong&gt;：提交、取消等按钮的文案应清晰，位置明确，避免歧义。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;禅意四可访问性-accessibility--兼爱非攻&#34;&gt;禅意四：可访问性 (Accessibility) —— 「兼爱非攻」&lt;a class=&#34;anchor&#34; href=&#34;#%e7%a6%85%e6%84%8f%e5%9b%9b%e5%8f%af%e8%ae%bf%e9%97%ae%e6%80%a7-accessibility--%e5%85%bc%e7%88%b1%e9%9d%9e%e6%94%bb&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;痛点&lt;/strong&gt;：表单对残障用户不友好，屏幕阅读器无法正确朗读，键盘用户无法操作。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;禅道&lt;/strong&gt;：让你的表单，能够被所有人无障碍地使用。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;语义化 HTML&lt;/strong&gt;：使用正确的 HTML 标签（&lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;），而不是滥用 &lt;code&gt;div&lt;/code&gt;。Angular 的 &lt;code&gt;FormControl&lt;/code&gt; 默认会为你添加许多必要的 ARIA 属性。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;键盘导航&lt;/strong&gt;：确保 Tab 键的焦点顺序逻辑正确。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;屏幕阅读器优化&lt;/strong&gt;：为自定义控件提供 &lt;code&gt;aria-label&lt;/code&gt;, &lt;code&gt;aria-describedby&lt;/code&gt; 等属性。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
