Привет,
В последней статье мы создали загрузчик аватаров. На этот раз я хочу показать вам, ребята, как заставить этот (или любой другой) компонент вести себя как элемент управления формы, чтобы его можно было использовать вместе с реактивными формами Angular или формами, управляемыми шаблонами. Он также может работать с валидаторами и так далее.
Превращение компонентов в элементы управления форм упрощает вашу жизнь, если вам нужно извлечь из них значение и использовать его для чего-то другого (например, для отправки запроса POST в REST API).
Вот что мы будем строить:
Исходный код здесь.
Средство доступа к управляющему значению
Первое, что нам нужно сделать, это сделать так, чтобы наш компонент реализовал класс ControlValueAccessor.
Я добавил ControlValueAcessor после OnInit и импортировал его:
import { ControlValueAccessor } from '@angular/forms';
Затем добавьте провайдера в аннотацию @ Component и импортируйте NG_VALUE_ACCESSOR.
@Component({ selector: 'app-avatar', templateUrl: './avatar.component.html', styleUrls: ['./avatar.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, multi: true, useExisting: AvatarComponent } ] })
Обратите внимание, что теперь AvatarComponent выдает ошибку:
Давайте будем хитрыми здесь, хорошо? Просто нажмите Быстрое исправление (или нажмите Ctrl + . ), затем нажмите Enter, среда IDE (надеюсь) создаст набор функций, как показано ниже.
writeValue(obj: any): void { throw new Error('Method not implemented.'); } registerOnChange(fn: any): void { throw new Error('Method not implemented.'); } registerOnTouched(fn: any): void { throw new Error('Method not implemented.'); } setDisabledState?(isDisabled: boolean): void { throw new Error('Method not implemented.'); }
Если этот трюк не работает, вы можете написать их вручную.
Методы
Вам может быть интересно, что на самом деле делают эти 4 функции.
Итак… вот что они делают:
(Благодарности Университету Angular)
- writeValue: этот метод вызывается модулем Forms для записи значения в элемент управления формы.
- registerOnChange: когда значение формы изменяется из-за пользовательского ввода, мы должны сообщить об этом значении родительской форме. Это делается путем вызова обратного вызова, который изначально был зарегистрирован в элементе управления с помощью метода registerOnChange.
- registerOnTouched: когда пользователь впервые взаимодействует с элементом управления формы, считается, что элемент управления имеет статус касания, что полезно для стиля. Для того, чтобы сообщить родительской форме о касании элемента управления, нам нужно использовать обратный вызов, зарегистрированный с помощью метода registerOnToched.
- setDisabledState: элементы управления формой можно включать и отключать с помощью Forms API. Это состояние можно передать в элемент управления формы с помощью метода setDisabledState.
По сути:
Angular Form Api будет вызывать эти методы и отправлять значения в наш компонент через параметры.
writeValue(obj: any): void {}
Давайте проанализируем writeValue. Он получает «obj» в качестве параметра. Этот метод будет вызываться Angular, когда значение установлено для нашего элемента управления формой в хост-компоненте.
В приведенном ниже примере я инициализирую форму и устанавливаю значение (строку URL) для нашего аватара.
Затем будет выполнено writeValue, и URL-адрес будет параметром «obj».
Что нам нужно сделать?
- В writeValue установите внутреннее значение нашего компонента на полученное значение из параметра.
- Создайте внутренний метод, который будет получать метод из параметра registerOnChange.
- Создайте внутренний метод, который будет получать метод из параметра registerOnTouched.
- Создайте внутреннюю переменную с именем disabled и внутри setDisabledState установите значение параметра для внутренней переменной. (на самом деле это необязательно, если ваш пользовательский элемент управления формой не находится в отключенном состоянии).
onChange = (fileUrl: string) => {}; onTouched = () => {}; disabled: boolean = false; writeValue(_file: string): void { this.file = _file; } registerOnChange(fn: any): void { this.onChange = fn; } registerOnTouched(fn: any): void { this.onTouched = fn; } setDisabledState?(isDisabled: boolean): void { this.disabled = isDisabled; }
Наконец, все, что вам нужно сделать, это каждый раз, когда значение вашего компонента изменяется, вызывать onChange(), чтобы уведомить Angular об изменении значения, например:
onFileChange(event: any) { const files = event.target.files as FileList; if (files.length > 0) { const _file = URL.createObjectURL(files[0]); this.resetInput(); this.openAvatarEditor(_file) .subscribe( (result) => { if(result){ this.file = result; this.onChange(this.file); // <= HERE } } ) } }
Использование компонента внутри формы
Сначала вы создаете группу форм и инициализируете ее с помощью FormBuilder (вы также можете использовать форму, управляемую шаблоном). Функция отправки будет вызываться нашей кнопкой «Сохранить» и будет регистрировать значения формы. В реальном мире вы, вероятно, сделали бы запрос API для сохранения данных формы.
profile.component.ts
@Component({ selector: 'app-profile', templateUrl: './profile.component.html', styleUrls: ['./profile.component.scss'] }) export class ProfileComponent implements OnInit { form!: FormGroup; constructor(private fb: FormBuilder) { } ngOnInit(): void { this.form = this.fb.group({ name: '', lastName: '', email: '', avatar: '' }) } submit(){ console.log(this.form.value); } }
Затем добавьте компонент внутрь формы и передайте ему formControlName в соответствии с объявлением формы в файле .ts.
profile.component.html
<form [formGroup]="form" (ngSubmit)="submit()"> <app-avatar formControlName="avatar"></app-avatar> //Now you can pass a formControlName to your component! <div class="row margin-top"> <mat-form-field appearance="fill"> <mat-label>First name</mat-label> <input type="text" matInput formControlName="name"> </mat-form-field> <mat-form-field appearance="fill"> <mat-label>Last name</mat-label> <input type="text" matInput formControlName="lastName"> </mat-form-field> </div> <div class="row"> <mat-form-field appearance="fill"> <mat-label>Email</mat-label> <input type="email" matInput formControlName="email"> </mat-form-field> </div> <div class="row"> <button mat-flat-button color="primary" type="submit">Save</button> </div> </form>
Пробовать это
Теперь вы можете легко получить доступ к значению вашего компонента и использовать его по своему усмотрению.
Вы также можете добавить к нему валидаторы.
Предположим, наш аватар требуется в форме.
ngOnInit(): void { this.form = this.fb.group({ name: '', lastName: '', email: '', avatar: ['', Validators.required] }) }
После подачи:
Конец
Исходный код здесь.
Надеюсь, это было полезно, увидимся в следующий раз!