หมายถึงข้อผิดพลาดที่เชิงมุมไม่ทราบว่าจะทำอย่างไรเมื่อคุณใส่ในformControl
div
ในการแก้ไขปัญหานี้คุณมีสองตัวเลือก
- คุณใส่
formControlName
องค์ประกอบบนซึ่งได้รับการสนับสนุนโดย Angular ออกจากกล่อง เหล่านี้คือ: input
, และtextarea
select
- คุณใช้
ControlValueAccessor
อินเทอร์เฟซ ด้วยการทำเช่นนี้คุณกำลังบอก Angular "วิธีเข้าถึงคุณค่าของการควบคุมของคุณ" (ชื่อจึงเป็นเช่นนั้น) หรือพูดง่ายๆ: สิ่งที่ต้องทำเมื่อคุณใส่formControlName
องค์ประกอบที่ไม่มีคุณค่าเกี่ยวข้องกับมันตามธรรมชาติ
ตอนนี้การใช้ControlValueAccessor
อินเทอร์เฟซอาจเป็นบิตที่น่ากลัวในตอนแรก โดยเฉพาะอย่างยิ่งเนื่องจากไม่มีเอกสารที่ดีเกี่ยวกับเรื่องนี้และคุณต้องเพิ่มจำนวนมากในรหัสของคุณ ดังนั้นให้ฉันลองทำตามขั้นตอนง่าย ๆ
ย้ายการควบคุมแบบฟอร์มของคุณไปยังองค์ประกอบของตัวเอง
เพื่อที่จะใช้งาน ControlValueAccessor
คุณจะต้องสร้างองค์ประกอบใหม่ (หรือคำสั่ง) ย้ายรหัสที่เกี่ยวข้องกับการควบคุมแบบฟอร์มของคุณไปที่นั่น เช่นนี้มันจะสามารถนำมาใช้ใหม่ได้อย่างง่ายดาย การมีการควบคุมอยู่ในองค์ประกอบอยู่แล้วอาจเป็นเหตุผลแรกที่คุณต้องใช้ControlValueAccessor
อินเทอร์เฟซเพราะไม่เช่นนั้นคุณจะไม่สามารถใช้องค์ประกอบที่กำหนดเองของคุณพร้อมกับฟอร์ม Angular
เพิ่มสำเร็จรูปลงในรหัสของคุณ
การใช้ControlValueAccessor
อินเทอร์เฟซค่อนข้างละเอียดนี่คือต้นแบบที่มาพร้อมกับมัน:
import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';
@Component({
selector: 'app-custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.scss'],
// a) copy paste this providers property (adjust the component name in the forward ref)
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
// b) Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {
// c) copy paste this code
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
// d) copy paste this code
writeValue(input: string) {
// TODO
}
ดังนั้นแต่ละส่วนทำอะไร
- a) ให้ Angular ทราบระหว่างรันไทม์ที่คุณใช้
ControlValueAccessor
อินเตอร์เฟส
- b) ทำให้แน่ใจว่าคุณกำลังใช้งาน
ControlValueAccessor
อินเทอร์เฟซ
- c) นี่อาจเป็นส่วนที่สับสนที่สุด โดยทั่วไปสิ่งที่คุณกำลังทำคือคุณให้ Angular มีความหมายในการแทนที่คุณสมบัติ / วิธีการเรียนของคุณ
onChange
และonTouch
ด้วยการใช้งานของมันเองในช่วงรันไทม์ดังนั้นคุณจึงสามารถเรียกใช้ฟังก์ชันเหล่านั้นได้ ดังนั้นประเด็นนี้มีความสำคัญที่จะเข้าใจ: คุณไม่จำเป็นต้องใช้ onChange และ onTouch ด้วยตัวคุณเอง (นอกเหนือจากการใช้งานเริ่มต้นที่ว่างเปล่า) สิ่งเดียวที่คุณทำกับ (c) คือให้ Angular แนบฟังก์ชันของตัวเองกับคลาสของคุณ ทำไม? ดังนั้นคุณก็จะสามารถโทรonChange
และonTouch
วิธีการให้บริการโดยเชิงมุมในเวลาที่เหมาะสม เราจะเห็นวิธีการทำงานด้านล่าง
- d) นอกจากนี้เรายังจะเห็นวิธี
writeValue
การทำงานของวิธีการในส่วนถัดไปเมื่อเราใช้งาน ฉันใส่มันไว้ที่นี่คุณสมบัติที่จำเป็นทั้งหมดControlValueAccessor
จะถูกนำไปใช้งานและโค้ดของคุณยังคงรวบรวม
ใช้ writeValue
อะไรwriteValue
ไม่คือการทำสิ่งที่อยู่ภายในองค์ประกอบที่กำหนดเองของคุณเมื่อมีการควบคุมรูปแบบที่มีการเปลี่ยนแปลงที่อยู่ข้างนอก ตัวอย่างเช่นหากคุณตั้งชื่อองค์ประกอบการควบคุมรูปแบบที่กำหนดเองapp-custom-input
และคุณจะใช้มันในองค์ประกอบหลักเช่นนี้:
<form [formGroup]="form">
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
จากนั้นได้รับการเรียกองค์ประกอบเมื่อใดก็ตามที่ผู้ปกครองอย่างใดเปลี่ยนค่าของwriteValue
myFormControl
นี่อาจเป็นตัวอย่างในระหว่างการเริ่มต้นของแบบฟอร์ม (this.form = this.formBuilder.group({myFormControl: ""});
) this.form.reset();
หรือในการตั้งค่ารูปแบบ
สิ่งที่คุณมักจะต้องการทำถ้าค่าของการควบคุมการเปลี่ยนแปลงรูปแบบด้านนอกคือการเขียนให้ตัวแปรท้องถิ่นซึ่งแสดงถึงค่าการควบคุมแบบฟอร์ม ตัวอย่างเช่นหากคุณCustomInputComponent
หมุนรอบตัวควบคุมแบบข้อความข้อความอาจมีลักษณะเช่นนี้:
writeValue(input: string) {
this.input = input;
}
และใน html ของCustomInputComponent
:
<input type="text"
[ngModel]="input">
คุณสามารถเขียนโดยตรงไปยังองค์ประกอบอินพุตตามที่อธิบายไว้ในเอกสารเชิงมุม
ตอนนี้คุณได้จัดการสิ่งที่เกิดขึ้นภายในองค์ประกอบของคุณเมื่อมีอะไรเปลี่ยนแปลงภายนอก ทีนี้ลองดูที่ทิศทางอื่น คุณจะแจ้งโลกภายนอกได้อย่างไรเมื่อมีบางสิ่งเปลี่ยนแปลงภายในองค์ประกอบของคุณ
โทรหาที่เปลี่ยน
CustomInputComponent
ขั้นตอนต่อไปคือการแจ้งให้องค์ประกอบของผู้ปกครองเกี่ยวกับการเปลี่ยนแปลงภายในของคุณ นี่คือที่onChange
และonTouch
ฟังก์ชั่นจาก (c) จากด้านบนเข้ามาเล่น โดยการเรียกฟังก์ชั่นเหล่านั้นคุณสามารถแจ้งภายนอกเกี่ยวกับการเปลี่ยนแปลงภายในส่วนประกอบของคุณ เพื่อเผยแพร่การเปลี่ยนแปลงของค่าที่ออกไปข้างนอกคุณจะต้องเรียก onChange ด้วยค่าใหม่เป็นอาร์กิวเมนต์ ตัวอย่างเช่นหากผู้ใช้พิมพ์บางอย่างในinput
ฟิลด์ในคอมโพเนนต์ที่กำหนดเองของคุณคุณจะโทรหาonChange
ด้วยค่าที่อัปเดต:
<input type="text"
[ngModel]="input"
(ngModelChange)="onChange($event)">
หากคุณตรวจสอบการใช้งาน (c) จากด้านบนอีกครั้งคุณจะเห็นสิ่งที่เกิดขึ้น: เชิงมุมผูกติดกับการใช้งานของonChange
คุณสมบัติคลาส การใช้งานนั้นคาดว่าจะมีหนึ่งอาร์กิวเมนต์ซึ่งเป็นค่าควบคุมที่อัปเดต สิ่งที่คุณกำลังทำอยู่ตอนนี้คือคุณกำลังเรียกวิธีการนั้นและแจ้งให้ Angular ทราบเกี่ยวกับการเปลี่ยนแปลง ตอนนี้ Angular จะดำเนินต่อไปและเปลี่ยนค่าของฟอร์มด้านนอก นี่คือส่วนสำคัญในทั้งหมดนี้ คุณบอก Angular เมื่อมันควรจะปรับปรุงการควบคุมแบบฟอร์มและสิ่งที่มีค่าโดยการโทรonChange
คุณบอกเชิงมุมเมื่อมันควรปรับปรุงการควบคุมรูปแบบและมีสิ่งที่มีค่าโดยการโทรคุณได้ให้วิธีการ "เข้าถึงค่าควบคุม"
โดยวิธีการ: ชื่อonChange
ถูกเลือกโดยฉัน คุณสามารถเลือกอะไรก็ได้ที่นี่เช่นpropagateChange
หรือคล้ายกัน อย่างไรก็ตามคุณตั้งชื่อมันว่ามันจะเป็นฟังก์ชั่นเดียวกับที่รับหนึ่งอาร์กิวเมนต์ที่จัดทำโดย Angular และที่ถูกผูกไว้กับคลาสของคุณด้วยregisterOnChange
วิธีการระหว่างรันไทม์
โทรบนทัช
เนื่องจากการควบคุมแบบฟอร์มสามารถ "สัมผัส" ได้คุณจึงควรให้ Angular มีวิธีการที่จะเข้าใจเมื่อการควบคุมฟอร์มที่กำหนดเองของคุณถูกแตะ คุณสามารถทำได้คุณเดาได้โดยการเรียกonTouch
ฟังก์ชั่น ดังนั้นสำหรับตัวอย่างของเราที่นี่หากคุณต้องการให้สอดคล้องกับวิธีการทำ Angular สำหรับการควบคุมแบบฟอร์มนอกกล่องคุณควรเรียกonTouch
เมื่อช่องสัญญาณเบลอ:
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
อีกครั้ง onTouch
เป็นชื่อที่ฉันเลือก แต่สิ่งที่จริง ๆ แล้วมันคือฟังก์ชั่นที่จัดทำโดย Angular และใช้ศูนย์อาร์กิวเมนต์ ซึ่งเหมาะสมแล้วเนื่องจากคุณเพียงแจ้งให้ Angular ทราบว่าการควบคุมฟอร์มได้รับการสัมผัสแล้ว
วางมันทั้งหมดเข้าด้วยกัน
แล้วมันจะดูอย่างไรเมื่อมันมารวมกัน? ควรมีลักษณะเช่นนี้:
// custom-input.component.ts
import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';
@Component({
selector: 'app-custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.scss'],
// Step 1: copy paste this providers property
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
// Step 2: Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {
// Step 3: Copy paste this stuff here
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
// Step 4: Define what should happen in this component, if something changes outside
input: string;
writeValue(input: string) {
this.input = input;
}
// Step 5: Handle what should happen on the outside, if something changes on the inside
// in this simple case, we've handled all of that in the .html
// a) we've bound to the local variable with ngModel
// b) we emit to the ouside by calling onChange on ngModelChange
}
// custom-input.component.html
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
// parent.component.html
<app-custom-input [formControl]="inputTwo"></app-custom-input>
// OR
<form [formGroup]="form" >
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
ตัวอย่างเพิ่มเติม
แบบฟอร์มซ้อน
โปรดทราบว่าการควบคุมค่า Accessors ไม่ใช่เครื่องมือที่เหมาะสมสำหรับกลุ่มฟอร์มที่ซ้อนกัน สำหรับกลุ่มฟอร์มที่ซ้อนกันคุณสามารถใช้@Input() subform
แทนได้ ค่าการควบคุม Accessorsors มีไว้เพื่อห่อcontrols
ไม่ใช่groups
! ดูตัวอย่างนี้วิธีใช้อินพุตสำหรับฟอร์มซ้อน: https://stackblitz.com/edit/angular-nested-forms-input-2
แหล่งที่มา