Implementacja okien dialogowych formularzy Upsert w Angular

Dec 06 2022
Wiele aplikacji wymaga możliwości tworzenia i aktualizowania rekordów za pośrednictwem interfejsu użytkownika. Zwykle będziemy chcieli użyć tego samego komponentu okna dialogowego dla obu operacji.

Wiele aplikacji wymaga możliwości tworzenia i aktualizowania rekordów za pośrednictwem interfejsu użytkownika. Zwykle będziemy chcieli użyć tego samego komponentu okna dialogowego dla obu operacji. Termin „upsert” jest połączeniem słów „update” i „insert” i odnosi się do faktu, że w oknie dialogowym można utworzyć nowy rekord lub zaktualizować istniejący.

Zobaczmy, jak możemy zaimplementować tę funkcjonalność. Ja skorzystam z ngneat/dialogbiblioteki, ale ty możesz użyć czego chcesz. Najpierw utworzymy wspólny interfejs data, który musimy przekazać do naszego okna dialogowego:

export interface UpsertDialogData<T> {
  action: 'add' | 'edit';
  actions$: Record<
    UpsertDialogData['type'],
    (value: T) => Observable<any>
  >;
  currentValue: T | undefined;
}

Teraz utwórzmy podstawowy upsertkomponent okna dialogowego:

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]);
      });
  }
}

Użyjmy naszej klasy bazowej i utwórzmy okno dialogowe do wstawiania użytkownika:

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 }),
        },
      },
    });
  }

}