Для многих приложений требуется возможность создавать и обновлять записи через пользовательский интерфейс. Обычно мы хотим использовать один и тот же диалоговый компонент для обеих операций. Термин «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!