+1

Giới thiệu một thư viện custom Angular Reactive Forms và những lợi ích nó mang lại

Khi bạn là một Angular Dev, bạn đã bao giờ từ hỏi "Tại sao Angular Reactive Forms không support type", hay muốn rằng "API truy vấn form một cách reactively. Nó đã bỏ qua một số methods". Nhưng suy nghĩ của bạn hay mong muốn của bạn cũng như tôi vậy, và tôi đã tìm được một số phương pháp, thư viên giúp chúng ta thực hiện điều đó, bạn hãy xem có thể sử dụng được không nhé! Thư viện này mở rộng mọi Angular AbstractControl và cung cấp các tính năng không tồn tại trong bản gốc. Nó thêm types, reactive queries, và một số helper methods. Điều quan trọng nhất là bạn có thể bắt đầu sử dụng nó ngay hôm nay! Trong hầu hết các trường hợp, điều duy nhất bạn cần thay đổi là import path. Vì vậy, đừng lo lắng, không yêu cầu cấu trúc lại form, vì chúng sẽ giúp bạn.

1. Cách cài đặt

Chỉ với một command bạn đã có thể sử dụng được nó

npm install @ngneat/reactive-forms

2. Control Type

Mỗi AbstractControl lấy một generic, nó là type cho bất kỳ method hoặc property nào được Angular hoặc thư viện này hiển thị:

Sử dụng nó với một FormControl:

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl<string>('');
control.value$.subscribe(value => {
  // value is typed as string
});

Bạn hãy để ý nhé, chúng ta sẽ sử dụng @ngneat/reactive-forms thay vì reactive-forms của Angular

Sử dụng nó với một FormGroup:

import { FormGroup } from '@ngneat/reactive-forms';

interface Profile {
  firstName: string;
  lastName: string;
  address: {
    street: string;
    city: string;
  };
}

const profileForm = new FormGroup<Profile>({
  firstName: new FormControl(''),
  lastName: new FormControl(''),
  address: new FormGroup({
    street: new FormControl(''),
    city: new FormControl('')
  })
});

// Typed as Profile
profileForm.setValue(new Profile()); 

// Typed as Partial<Profile>
profileForm.patchValue({ firstName: 'Netanel' }); 

Sử dụng nó với FormArray:

import { FormArray, FormControl } from '@ngneat/reactive-forms';

const control = new FormArray<string>([new FormControl('')]);

control.value$.subscribe(value => {
  // value is typed as string[]
});

3. Control Queries

value$

Observes the control’s value. Không giống như behavior của valueChanges observable, emits the current rawValue immediately (có nghĩa là bạn cũng sẽ nhận được các giá trị của các control bị disable)

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.value$.subscribe(value => ...);

disable$

Observes trạng thái disable của controls

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.disabled$.subscribe(isDisabled => ...);

status$

Observes trạng thái của các control

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.status$.subscribe(status => ...);

Lưu ý: biến trạng thái ở đây thuộc loại ControlState (valid, invalid, pending or disabled)

touch$

Observes trạng thái touch của controls

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.touch$.subscribe(isTouched => ...);

Nó sẽ chỉ emit một value khi markAsTouched hoặc markAsUnTouched được gọi.

dirty$

Observes trạng thái dirty của controls

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.dirty$.subscribe(isDirty => ...);

Nó sẽ chỉ emit một value khi markAsDirty hoặc markAsPristine được gọi.

errors$

Observes các error của controls

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.errors$.subscribe(errors => ...);

select()

Lựa chọn một slice của form states dựa trên predicate đã cho.

import { FormGroup } from '@ngneat/reactive-forms';

const control = new FormGroup<Person>(...);
control.select(form => form.name).subscribe(name => ...)

3. Control Methods

setValue()

Ngoài method functionality, nó cũng có thể observable.

import { FormGroup } from '@ngneat/reactive-forms';

const control = new FormGroup<Person>();
control.setValue(query.select('formValue'));

disabledWhile()

Đưa ra một observable emits một boolean cho biết có nên disable control hay không

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.disabledWhile(query.select('isDisabled'));

enabledWhile()

Đưa ra một observable emits một boolean cho biết có nên enable control hay không

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.enabledWhile(query.select('isEnabled'));

mergeValidators()

Không giống như phương thức setValidator() được tích hợp sẵn, nó vẫn tồn tại bất kỳ validators nào hiện có.

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('', Validators.required);
control.mergeValidators(Validators.minLength(2));
control.mergeAsyncValidators(...);

markAllAsDirty()

Đánh dấu tất cả các controls của nhóm là dirty.

import { FormGroup } from '@ngneat/reactive-forms';

const control = new FormGroup<Person>();
control.markAllAsDirty();

validateOn()

Lấy một đối tượng observable emits một response, là null hoặc error object (ValidationErrors)

const passwordValidator = combineLatest([
  this.signup.select(state => state.password),
  this.signup.select(state => state.repeatPassword)
]).pipe(
  map(([password, repeat]) => {
    return password === repeat
      ? null
      : {
          isEqual: false
        };
  })
);

this.signup.validateOn(passwordValidator);

hasErrorAndTouched()

import { FormControl } from '@ngneat/reactive-forms';

this.control = new FormControl('', Validators.required);

Cái tên nói lên tất cả, bạn có thể đùng nó trong template như sau

<span *ngIf="control.hasErrorAndTouched('required')"></span>

tương tự ta có

hasErrorAndDirty()

import { FormControl } from '@ngneat/reactive-forms';

this.control = new FormControl('', Validators.required);
<span *ngIf="control.hasErrorAndDirty('required')"></span>

setEnable()

Đặt control ở trạng thái enabled

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.setEnable();
control.setEnable(false);

setDisable()

Đặt control ở trạng thái disabled

import { FormControl } from '@ngneat/reactive-forms';

const control = new FormControl('');
control.setDisable();
control.setDisable(false);

getControl()

Một method với các tham số đã nhập nhận tham chiếu đến một control cụ thể.

import { FormGroup } from '@ngneat/reactive-forms';

const group = new FormGroup<Profile>(...);
const address = group.getControl('name') as FormGroup<Profile['address']>;
const city = group.getControl('address', 'city') as FormControl<string>;

Control Path

Array path biến thể của hasError(), getError(), và get() hiện tại typed:

const num = group.get(['phone', 'num']) as FormControl<string>;
const hasError = group.hasError('required', ['phone', 'num']);
const getError = group.getError('required', ['phone', 'num']);

4. Control Errors

Mỗi AbstractControl có một generic thứ hai, đóng vai trò là loại errors:

type MyErrors = { isEqual: false };

const control = new FormControl<string, MyErrors>();
control.getError('isEqual'); // keyof MyErrors
control.hasError('isEqual'); // keyof MyErrors

// error type is MyErrors['isEqual']
const error = control.getError('isEqual'); // keyof MyErrors

Thư viện cung cấp một kiểu cho các Angular validators được tích hợp sẵn:

import { FormControl, NgValidatorsErrors } from '@ngneat/reactive-forms';

const control = new FormControl<string, NgValidatorsErrors>();

5. ControlValueAccessor

Thư viện hiển thị một typed version của ControlValueAccessor, đã implements registerOnChangeregisterOnTouched ở phía dưới

import { ControlValueAccessor } from '@ngneat/reactive-forms';

@Component({
 selector: 'my-checkbox',
 host: { '(change)': 'onChange($event.target.checked)', '(blur)': 'onTouched()' },
 providers: [
   {
     provide: NG_VALUE_ACCESSOR,
     useExisting: MyCheckboxComponent,
     multi: true
   }
 ]
})
export class MyCheckboxComponent extends ControlValueAccessor<boolean> {
 writeValue(value) {
   // value is typed a boolean
 }

 // `this.onChange`, and `this.onTouched` are already here!
}

Lưu ý rằng bạn cũng có thể sử dụng nó làm interface.

6. Form Builder

Thư viện cũng giới thiệu một type version của FormBuilder, trả về một FormGroup, FormControl và FormArray với tất cả các additions thú vị

import { FormBuilder } from '@ngneat/reactive-forms';

const fb = new FormBuilder();
// Returns a FormGroup<{name: string, id: number}>
const group = fb.group({ name: 'ngneat', id: 1 });

interface User {
  userName: string;
  email: string;
}

// We'll get an error because "id" does not exist in type `User`
const userGroup: FormGroup<User> = fb.group({ id: 1, userName: 'User', email: 'Email' });

Lưu ý: Trong khi FormGroups / FormControls / etc được tạo bằng FormBuilder của thư viện sẽ có tất cả các additions, hiện tại TS sẽ không suy luận điều này, vì vậy người ta vẫn nên 'cast' lại chúng khi sử dụng:

const group = fb.group({
  userName: null,
  email: null
});

// will get TS error
group.controls.email.errors$.subscribe();

// will not get TS error
(group.controls.email as FormControl<string>).errors$.subscribe();

7. ESLint Rule

Thư viện cũng cung cấp các quy tắc lint đặc biệt ngăn việc import các mã mà thư viện có từ @angular/form, ví dụ như AbstractControl, AsyncValidatorFn, ControlValueAccessor, FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn

8. Migration

Lệnh sẽ thay thế các entities từ @angular/reactive-forms bằng @ngneat/reactive-forms

ng g @ngneat/reactive-forms:migrate

Thông tin thêm về script có thể được tìm thấy ở đây.

9. Kết luận

Bài viết giới thiệu cho các bạn một thư việc có ích giúp việc xử lý logic với Reactive Form trong Angular được dễ dàng hơn hi vọng nó có thể được áp dụng tốt vào dự án của các bạn

Lưu ý : Reactive-form tương thích với Angular phiên bản 8 trở lên.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.