Для многих приложений требуется возможность создавать и обновлять записи через пользовательский интерфейс. Обычно мы хотим использовать один и тот же диалоговый компонент для обеих операций. Термин «upsert» представляет собой комбинацию слов «обновить» и «вставить» и относится к тому факту, что диалоговое окно можно использовать как для создания новой записи, так и для обновления существующей.
Давайте посмотрим, как мы можем реализовать эту функцию. Я буду использовать библиотеку ngneat/dialog
, но вы можете использовать что угодно. Во-первых, мы создадим общий интерфейс для data
, который нам нужно передать в наш диалог:
export interface UpsertDialogData<T> { action: 'add' | 'edit'; actions$: Record< UpsertDialogData['type'], (value: T) => Observable<any> >; currentValue: T | undefined; }
Мы передаем тип операции action
и тип actions
, который хотим выполнить. Кроме того, мы передаем текущее значение для случая edit
. Мы могли бы сделать его более строгим, сузив action
только для разрешения свойства currentValue
, когда мы находимся в режиме edit
, но я буду упрощать.
Теперь давайте создадим базовый диалоговый компонент upsert
:
import { DialogRef } from '@ngneat/dialog'; import { loadingFor } from '@ngneat/loadoff'; import { HotToastService } from '@ngneat/hot-toast'; @Directive() export abstract class UpsertDialogComponent<Entity extends Record<string, any>> implements OnInit { private toast = inject(HotToastService); loader = loadingFor('upsert'); ref: DialogRef<UpsertDialogData<Entity>> = inject(DialogRef); abstract get form(): FormGroup; abstract getMessages(): Record<UpsertDialogData['action'], string>; get action() { return this.ref.data.action; } ngOnInit() { if (this.ref.data.action === 'edit') { this.form.patchValue(this.ref.data.currentValue); } } upsert() { if (this.form.invalid) return; this.ref.data.actions$[this.action](this.form.getRawValue()) .pipe(this.loader.upsert.track()) .subscribe(() => { this.ref.close(); this.toast.success(this.getMessages()[this.action]); }); } }
Каждый компонент, расширяющий этот класс, должен реализовать свойство form
и метод getMessages()
. Как только форма создана, мы обновляем начальное значение значением, полученным из диалогового окна data
, когда форма находится в режиме edit
. Значение формы передается соответствующему действию при отправке.
Давайте воспользуемся нашим базовым классом и создадим диалог для добавления пользователя:
import { errorTailorImports } from '@ngneat/error-tailor'; @Component({ standalone: true, imports: [errorTailorImports], templateUrl: `./upsert-user-dialog.component.html` }) export class UpsertUserDialogComponent extends UpsertDialogComponent<User> { form = inject(FormBuilder).nonNullable.group({ name: ['', Validators.required], }); getMessages() { return { add: `User was added successfully`, edit: `User was updated successfully`, }; } } <header> <h3>{{ action === 'edit' ? 'Edit User' : 'New User' }}</h3> </header> <div> <form [formGroup]="form" errorTailor> <input placeholder="Name" formControlName="name" /> </form> </div> <footer> <button dialogClose>Cancel</button> <button [loading]="loader.upsert.inProgress$ | async" (click)="upsert()" > {{ action === 'edit' ? 'Save' : 'Create' }} </button> </footer>
Нам остается только открыть наш диалог и передать соответствующую информацию:
@Component({ template: ` <button (click)="openUpsertUserDialog('add')">Add User</button> <-- ngFor --> <button (click)="openUpsertUserDialog('edit', user)">Edit</button> ` }) export class UsersPageComponent { openUpsertUserDialog( action: UpsertDialogData['action'], currentValue?: UpsertDialogData<User>['currentValue'] ) { this.dialogService.open(UpsertUserDialogComponent, { data: { action, currentValue, actions$: { add: (user) => this.usersService.addUser({ user}), edit: (user) => this.usersService.updateUser({ user }), }, }, }); } }
Подпишитесь на меня в Medium или Twitter, чтобы узнать больше об Angular и JS!