杨柳高舞楼心月 發表於 2020-6-19 15:01:00

angular 更新表单值的两种方法: setvalue,patchvalue

<p><strong>使用 patchValue() 方法会比使用 setValue() 方法更好!</strong></p>
<p>&nbsp;</p>
<p><strong>1、patchValue()</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> angular2/packages/forms/src/model.ts</span>
<span style="color: rgba(0, 0, 0, 1)">export class FormGroup extends AbstractControl {
   ...
   patchValue(
   value: {: any},{onlySelf, emitEvent}:
            {onlySelf</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span>, emitEvent?: <span style="color: rgba(0, 0, 255, 1)">boolean</span>} = {}): <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> {
      Object.keys(value).forEach(name </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.controls) {
          </span><span style="color: rgba(0, 0, 255, 1)">this</span>.controls.patchValue(value, {onlySelf: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">, emitEvent});
      }
      });
      </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.updateValueAndValidity({onlySelf, emitEvent});
   }
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用示例</span>
const form = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FormGroup({
   first: </span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FormControl(),
   last: </span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FormControl()
});

console.log(form.value);   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> {first: null, last: null}</span>
<span style="color: rgba(0, 0, 0, 1)">
form.patchValue({first: </span>'Nancy'<span style="color: rgba(0, 0, 0, 1)">});
console.log(form.value);   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> {first: 'Nancy', last: null}</span></pre>
</div>
<p>从源码中我们可以看出,patchValue() 方法会获取输入参数对象的所有 key 值,然后循环调用内部控件的&nbsp;<code>patchValue()</code>&nbsp;方法,具体代码如下:</p>
<div class="cnblogs_code">
<pre>Object.keys(value).forEach(name =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.controls) {
   </span><span style="color: rgba(0, 0, 255, 1)">this</span>.controls.patchValue(value, {onlySelf: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">, emitEvent});
}
});</span></pre>
</div>
<p>首先,<code>Object.keys()</code>&nbsp;会返回对象 key 值的数组,例如:</p>
<div class="cnblogs_code">
<pre>const man = {name : 'Semlinker', age: 30<span style="color: rgba(0, 0, 0, 1)">};
Object.keys(man); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ['name', 'age']</span>此外 <code style="font-style: italic; background-color: rgba(255, 255, 255, 1); font-size: 14px">this.controls</code><span style="font-style: italic; background-color: rgba(255, 255, 255, 1); font-family: &quot;PingFang SC&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 14px"> 包含了 FormGroup 对象中的所有 FormControl 控件,我们可以通过 </span><code style="font-style: italic; background-color: rgba(255, 255, 255, 1); font-size: 14px">this.controls</code><span style="font-style: italic; background-color: rgba(255, 255, 255, 1); font-family: &quot;PingFang SC&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 14px"> 方式,访问到 name 对应的控件对象。</span></pre>
</div>
<div>
<p>现在让我们来回顾一下创建 FormGroup 对象的相关代码</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">this</span>.form = <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.fb.group({
name: [</span>''<span style="color: rgba(0, 0, 0, 1)">, Validators.required],
event: </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.fb.group({
    title: [</span>''<span style="color: rgba(0, 0, 0, 1)">, Validators.required],
    location: [</span>''<span style="color: rgba(0, 0, 0, 1)">, Validators.required]
})
});</span></pre>
</div>
<p>与之相对应的对象模型如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
name: </span>''<span style="color: rgba(0, 0, 0, 1)">,
event: {
    title: </span>''<span style="color: rgba(0, 0, 0, 1)">,
    location: </span>''<span style="color: rgba(0, 0, 0, 1)">
}
}</span></pre>
</div>
<p>因此要更新该模型的值,我们可以利用&nbsp;<code>FormGroup</code>&nbsp;对象的&nbsp;<code>patchValue()</code>&nbsp;方法:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.form.patchValue({
name: </span>'Semlinker'<span style="color: rgba(0, 0, 0, 1)">,
event: {
    title: </span>'Angular 4.x\'s Road'<span style="color: rgba(0, 0, 0, 1)">,
    location: </span>'Xiamen'<span style="color: rgba(0, 0, 0, 1)">
}
});</span></pre>
</div>
<p>以上代码将会通过循环的方式,更新每个&nbsp;<code>FormControl</code>&nbsp;控件。接下来我们看一下&nbsp;<code>FormControl</code>&nbsp;中 patchValue() 方法的具体实现:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">patchValue(value: any, options: {
    onlySelf</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">,
    emitEvent</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">,
    emitModelToViewChange</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">,
    emitViewToModelChange</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">
} </span>= {}): <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.setValue(value, options);
}</span></pre>
</div>
<div>
<div>忽略所有的函数参数和类型,它所做的就是调用 setValue() 方法,设置控件的值。另外使用 <code>patchValue()</code> 方法有什么好处呢?假设我们使用 <code>firebase</code>,那么当我们从 API 接口获取数据对象时,对象内部可能会包含 <code>$exists</code> 和 <code>$key</code> 属性。而当我们直接使用返回的数据对象作为参数,直接调用 patchValue() 方法时,不会抛出任何异常:</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.form.patchValue({
$exists: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {},
$key: </span>'-KWihhw-f1kw-ULPG1ei'<span style="color: rgba(0, 0, 0, 1)">,
name: </span>'Semlinker'<span style="color: rgba(0, 0, 0, 1)">,
event: {
    title: </span>'Angular 4.x\'s Road'<span style="color: rgba(0, 0, 0, 1)">,
    location: </span>'Xiamen'<span style="color: rgba(0, 0, 0, 1)">
}
});</span></pre>
</div>
<p>其实没有抛出异常的原因,是因为在 patchValue() 内部循环时,我们有使用&nbsp;<code>if</code>&nbsp;语句进行条件判断。</p>
<p>&nbsp;</p>
<strong>2、setValue</strong><br>FormGroup 类中的 setValue() 方法的具体实现:</div>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">setValue(
value: {: any},
{onlySelf, emitEvent}: {onlySelf</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span>, emitEvent?: <span style="color: rgba(0, 0, 255, 1)">boolean</span>} = {}): <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">._checkAllValuesPresent(value);
      Object.keys(value).forEach(name </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">._throwIfControlMissing(name);
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.controls.setValue(value, {onlySelf: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">, emitEvent});
      });
      </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.updateValueAndValidity({onlySelf, emitEvent});
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用示例</span>
const form = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FormGroup({
    first: </span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FormControl(),
    last: </span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FormControl()
});
console.log(form.value);   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> {first: null, last: null}</span>
<span style="color: rgba(0, 0, 0, 1)">
form.setValue({first: </span>'Nancy', last: 'Drew'<span style="color: rgba(0, 0, 0, 1)">});
console.log(form.value);   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> {first: 'Nancy', last: 'Drew'}</span></pre>
</div>
<div>
<div>跟 patchValue() 方法一样,我们内部也是包含一个 <code>Object.keys()</code> 的循环,但在循环开始之前,我们会先调用 <code>_checkAllValuesPresent()</code> 方法,对输入值进行校验。 另外 <code>_checkAllValuesPresent()</code> 方法的具体实现如下:</div>
<div class="cnblogs_code">
<pre>_checkAllValuesPresent(value: any): <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>._forEachChild((control: AbstractControl, name: string) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (value ===<span style="color: rgba(0, 0, 0, 1)"> undefined) {
      </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Error(`Must supply a value <span style="color: rgba(0, 0, 255, 1)">for</span> form control <span style="color: rgba(0, 0, 255, 1)">with</span> name: '${name}'<span style="color: rgba(0, 0, 0, 1)">.`);
      }
    });
}</span></pre>
</div>
<div>
<div>
<p>该方法内部通过 <code>_forEachChild()</code> 遍历内部的 FormControl 控件,来确保我们在调用 <code>setValue()</code> 方法时,设置的参数对象中,会包含所有控件的配置信息。如果 <code>name</code> 对应的配置信息不存在,则会抛出异常。</p>
<p>在 <code>_checkAllValuesPresent()</code> 验证通过后,Angular 会进入 <code>Object.keys()</code> 循环,此外在调用 <code>setValue()</code> 方法前,还会优先调用 <code>_throwIfControlMissing()</code> 判断控件是否存在,该方法的实现如下:</p>
</div>
<div class="cnblogs_code">
<pre>_throwIfControlMissing(name: string): <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!Object.keys(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.controls).length) {
      </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Error(`
      There are no form controls registered </span><span style="color: rgba(0, 0, 255, 1)">with</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)"> group yet.
      If you</span>'<span style="color: rgba(0, 0, 0, 1)">re using ngModel,
      you may want to check next tick (e.g. use setTimeout).
      `);
    }
    if (!this.controls) {
      throw new Error(`Cannot find form control with name: ${name}.`);
    }
}</span></pre>
</div>
<div>
<div>上面代码首先判断 <code>this.controls</code> 是否存在,如果存在进一步判断 <code>name</code> 对应的 <code>FormControl</code> 控件是否存在。当 <code>_throwIfControlMissing()</code> 验证通过后,才会最终调用 <code>FormControl</code> 控件的 setValue() 方法:</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">this</span>.controls.setValue(value, {onlySelf: <span style="color: rgba(0, 0, 255, 1)">true</span>, emitEvent})</pre>
</div>
<p>我们来看一下&nbsp;<code>FormControl</code>&nbsp;类中,setValue() 方法的具体实现:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">setValue(value: any, {onlySelf, emitEvent, emitModelToViewChange,   
    emitViewToModelChange}: {
      onlySelf</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">,
      emitEvent</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">,
      emitModelToViewChange</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">,
      emitViewToModelChange</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">
} </span>= {}): <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>._value =<span style="color: rgba(0, 0, 0, 1)"> value;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>._onChange.length &amp;&amp; emitModelToViewChange !== <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>._onChange.forEach((changeFn) =&gt;<span style="color: rgba(0, 0, 0, 1)">
          changeFn(</span><span style="color: rgba(0, 0, 255, 1)">this</span>._value, emitViewToModelChange !== <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">));
    }
    </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.updateValueAndValidity({onlySelf, emitEvent});
}</span></pre>
</div>
<div>
<div>
<p>该方法的第一个参数,就是我们要设置的值,第二个参数是一个对象:</p>
<ul>
<li>onlySelf:若该值为 true,当控件的值发生变化后,只会影响当前控件的验证状态,而不会影响到它的父组件。默认值是 false。</li>
<li>emitEvent:若该值为 true,当控件的值发生变化后,将会触发 <code>valueChanges</code> 事件。默认值是 true</li>
<li>emitModelToViewChange:若该值为 true,当控件的值发生变化时,将会把新值通过 <code>onChange</code> 事件通知视图层。若未指定 <code>emitModelToViewChange</code> 的值,这是默认的行为。</li>
<li>emitViewToModelChange:若该值为 true,<code>ngModelChange</code> 事件将会被触发,用于更新模型。若未指定 <code>emitViewToModelChange</code> 的值,这是默认的行为。</li>
</ul>
<p>其实仅仅通过上面的代码,我们还是没完全搞清楚 <code>setValue()</code> 方法内部真正执行流程。如我们不知道如何注册 changeFn 函数和 <code>updateValueAndValidity()</code> 方法的内部处理逻辑,接下来我们先来看一下如何注册 changeFn 函数</p>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">export class FormControl extends AbstractControl {
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">* @internal </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
_onChange: Function[] </span>=<span style="color: rgba(0, 0, 0, 1)"> [];
...
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* Register a listener for change events.
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
registerOnChange(fn: Function): </span><span style="color: rgba(0, 0, 255, 1)">void</span> { <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">._onChange.push(fn); }
}</span></pre>
</div>
<p>现在我们来回顾一下 setValue() 的相关知识点。对于&nbsp;<code>FormGroup</code>&nbsp;对象,我们可以通过&nbsp;<code>setValue()</code>&nbsp;方法更新表单的值,具体使用示例如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.form.setValue({
name: </span>'Semlinker'<span style="color: rgba(0, 0, 0, 1)">,
event: {
    title: </span>'Angular 4.x\'s Road'<span style="color: rgba(0, 0, 0, 1)">,
    location: </span>'Xiamen'<span style="color: rgba(0, 0, 0, 1)">
}
});</span></pre>
</div>
<p>以上代码成功运行后,我们就能成功更新表单的值。但如果我们使用下面的方式,就会抛出异常:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.form.setValue({
$exists: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {},
$key: </span>'-KWihhw-f1kw-ULPG1ei'<span style="color: rgba(0, 0, 0, 1)">,
name: </span>'Semlinker'<span style="color: rgba(0, 0, 0, 1)">,
event: {
    title: </span>'Angular 4.x\'s Road'<span style="color: rgba(0, 0, 0, 1)">,
    location: </span>'Xiamen'<span style="color: rgba(0, 0, 0, 1)">
}
});</span></pre>
</div>
<p>&nbsp;</p>
总结:patchValue 可以只更新副本的数据,而setValue则必须与form 数据结构一致才能进行更新。<br><br></div>
</div>
</div>
</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/isylar/p/13163133.html
頁: [1]
查看完整版本: angular 更新表单值的两种方法: setvalue,patchvalue