"Reusable" 表格或至少是一种更简单的创建表格的方法

"Reusable" forms or at least an easier way to create them

我正在从事一个 Angular 12 项目,有人告诉我,我为 CRUD 操作创建表单的方式有点让人不知所措。我目前使用的方式就像official primeng example。请注意,有 7-8 个类似的 CRUD,唯一的区别是表单字段。

问题 1

我想告诉我的人可能是想用某种共享组件替换当前代码,然后枚举所有表单字段并显示它们?反应形式?或者也许有一些花哨的 npm 包?

问题 2

我正在研究 this guy did 它在那个开源项目中的表现。也许我应该使用他的方法?

代码

import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';

import { Lecturer } from '@core/types';
import { LecturerService } from './lecturer.service';
import { ConfirmationService, MessageService, PrimeNGConfig } from 'primeng/api';
import { BreadcrumbService } from '@core/services';
import { Table } from 'primeng/table';

@Component({
  selector: 'app-lecturers',
  templateUrl: './lecturers.component.html'
})
export class LecturersComponent implements OnInit, OnDestroy {
  lecturerDialog: boolean;
  lecturers: Lecturer[];
  lecturer: Lecturer;
  selectedLecturers: Lecturer[];
  submitted: boolean;
  loading: boolean = true;

  private componentDestroyed$ = new Subject<boolean>();

  @ViewChild('dt') table: Table;

  constructor(
    private lecturerService: LecturerService,
    private messageService: MessageService,
    private confirmationService: ConfirmationService,
    private breadcrumbService: BreadcrumbService,
    private primengConfig: PrimeNGConfig
  ) {
    this.breadcrumbService.setItems([{ label: 'Преподаватели' }]);
  }

  ngOnInit(): void {
    this.lecturerService
      .getAllLecturers()
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data) => {
        this.lecturers = data;
        this.loading = false;
      });

    this.primengConfig.ripple = true;
  }

  ngOnDestroy(): void {
    this.componentDestroyed$.next(true);
    this.componentDestroyed$.complete();
  }

  applyFilterGlobal($event: any) {
    this.table.filterGlobal(($event.target as HTMLInputElement).value, 'contains');
  }

  openNew() {
    this.lecturer = {};
    this.submitted = false;
    this.lecturerDialog = true;
  }

  deleteSelectedLecturers() {
    this.confirmationService.confirm({
      message: 'Сигурни ли сте, че искате да изтриете данните за избраните преподаватели?',
      header: 'Потвърждение',
      icon: 'pi pi-exclamation-triangle',
      acceptLabel: 'Да',
      rejectLabel: 'Не',
      accept: () => {
        this.lecturers = this.lecturers.filter((val) => !this.selectedLecturers.includes(val));
        this.selectedLecturers = [];
        this.messageService.add({
          severity: 'success',
          summary: 'Успешно',
          detail: 'Данните за преподавателите са изтрити',
          life: 3000
        });
      }
    });
  }

  editLecturer(lecturer: Lecturer) {
    this.lecturer = { ...lecturer };
    this.lecturerDialog = true;
  }

  deleteLecturer(lecturer: Lecturer) {
    this.confirmationService.confirm({
      message: 'Сигурни ли сте, че искате да изтриете данните за ' + lecturer.displayName + '?',
      header: 'Потвърждение',
      icon: 'pi pi-exclamation-triangle',
      acceptLabel: 'Да',
      rejectLabel: 'Не',
      accept: () => {
        this.lecturers = this.lecturers.filter((val) => val.id !== lecturer.id);
        this.lecturer = {};
        this.messageService.add({
          severity: 'success',
          summary: 'Успешно',
          detail: 'Данните за преподавателя са изтрити',
          life: 3000
        });
      }
    });
  }

  hideDialog() {
    this.lecturerDialog = false;
    this.submitted = false;
  }

  saveLecturer() {
    this.submitted = true;

    if (this.lecturer.fullName?.trim() && this.lecturer.displayName?.trim()) {
      if (this.lecturer.id) {
        this.lecturers[this.findIndexById(this.lecturer.id)] = this.lecturer;
        this.messageService.add({
          severity: 'success',
          summary: 'Успешно',
          detail: 'Данните за преподавателя са обновени',
          life: 3000
        });
      } else {
        this.lecturers.push(this.lecturer);

        // update to what's in db instead

        this.messageService.add({
          severity: 'success',
          summary: 'Успешно',
          detail: 'Преподавателят е добавен',
          life: 3000
        });
      }

      this.lecturers = [...this.lecturers];
      this.lecturerDialog = false;
      this.lecturer = {};
    }
  }

  findIndexById(id: number): number {
    let index = -1;
    for (let i = 0; i < this.lecturers.length; i++) {
      if (this.lecturers[i].id === id) {
        index = i;
        break;
      }
    }

    return index;
  }
}

<div class="p-grid">
  <div class="p-col-12">
    <p-toast></p-toast>

    <div class="card">
      <p-toolbar styleClass="p-mb-4">
        <ng-template pTemplate="left">
          <button
            pButton
            pRipple
            label="Добавяне"
            icon="pi pi-plus"
            class="p-button-success p-mr-2 p-mb-2"
            (click)="openNew()"
          ></button>
          <button
            pButton
            pRipple
            label="Изтриване"
            icon="pi pi-trash"
            class="p-button-danger p-mb-2"
            (click)="deleteSelectedLecturers()"
            [disabled]="!selectedLecturers || !selectedLecturers.length"
          ></button>
        </ng-template>
      </p-toolbar>

      <p-table
        #dt
        [value]="lecturers"
        [(selection)]="selectedLecturers"
        dataKey="id"
        styleClass="p-datatable-lecturers"
        [rowHover]="true"
        [rows]="10"
        [showCurrentPageReport]="true"
        [rowsPerPageOptions]="[10, 25, 50]"
        [loading]="loading"
        [paginator]="true"
        currentPageReportTemplate="Показват се от {first} до {last} от общо {totalRecords} записа"
        [globalFilterFields]="['fullName', 'displayName']"
      >
        <ng-template pTemplate="caption">
          <div class="p-d-flex p-flex-column p-flex-md-row p-jc-md-between table-header">
            <h5 class="p-m-0">Преподаватели</h5>
            <span class="p-input-icon-left">
              <i class="pi pi-search"></i>
              <input
                pInputText
                type="text"
                (input)="applyFilterGlobal($event)"
                placeholder="Търсене..."
              />
            </span>
          </div>
        </ng-template>
        <ng-template pTemplate="header">
          <tr>
            <th style="width: 3rem">
              <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
            </th>
            <th pSortableColumn="fullName">Име <p-sortIcon field="fullName"></p-sortIcon></th>
            <th pSortableColumn="displayName">
              Псевдоним <p-sortIcon field="displayName"></p-sortIcon>
            </th>
            <th></th>
          </tr>
        </ng-template>
        <ng-template pTemplate="body" let-lecturer>
          <tr>
            <td>
              <p-tableCheckbox [value]="lecturer"></p-tableCheckbox>
            </td>
            <td>
              {{ lecturer.displayName }}
            </td>
            <td>
              {{ lecturer.fullName }}
            </td>
            <td>
              <button
                pButton
                pRipple
                icon="pi pi-pencil"
                class="p-button-rounded p-button-success p-mr-2"
                (click)="editLecturer(lecturer)"
              ></button>
              <button
                pButton
                pRipple
                icon="pi pi-trash"
                class="p-button-rounded p-button-warning"
                (click)="deleteLecturer(lecturer)"
              ></button>
            </td>
          </tr>
        </ng-template>
        <ng-template pTemplate="summary">
          <div class="p-d-flex p-ai-center p-jc-between">
            Общо {{ lecturers ? lecturers.length : 0 }} преподаватели.
          </div>
        </ng-template>
      </p-table>
    </div>

    <p-dialog
      [(visible)]="lecturerDialog"
      [style]="{ width: '450px' }"
      header="Данни за преподавател"
      [modal]="true"
      styleClass="p-fluid"
    >
      <ng-template pTemplate="content">
        <div class="p-field">
          <label for="fullName">Име</label>
          <input
            #fullName="ngModel"
            id="fullName"
            type="text"
            pInputText
            [(ngModel)]="lecturer.fullName"
            [ngClass]="{
              'ng-dirty': (fullName.invalid && submitted) || (fullName.dirty && fullName.invalid)
            }"
            required
            autofocus
          />
          <small
            class="p-error"
            *ngIf="(fullName.invalid && submitted) || (fullName.dirty && fullName.invalid)"
            >Името е задължително.</small
          >
        </div>
        <div class="p-field">
          <label for="displayName">Псевдоним</label>
          <input
            #displayName="ngModel"
            id="displayName"
            type="text"
            pInputText
            [(ngModel)]="lecturer.displayName"
            [ngClass]="{
              'ng-dirty':
                (displayName.invalid && submitted) || (displayName.dirty && displayName.invalid)
            }"
            required
          />
          <small
            class="p-error"
            *ngIf="(displayName.invalid && submitted) || (displayName.dirty && displayName.invalid)"
            >Псевдонимът е задължителен.</small
          >
        </div>
      </ng-template>

      <ng-template pTemplate="footer">
        <button
          pButton
          pRipple
          label="Отказ"
          icon="pi pi-times"
          class="p-button-text"
          (click)="hideDialog()"
        ></button>
        <button
          pButton
          pRipple
          label="Запази"
          icon="pi pi-check"
          class="p-button-text"
          (click)="saveLecturer()"
        ></button>
      </ng-template>
    </p-dialog>

    <p-confirmDialog [style]="{ width: '450px' }"></p-confirmDialog>
  </div>
</div>

我建议将表单放在一个单独的组件中 - 直接将简化 table 组件

您可以使用 Angular Reactive Forms,它在后台管理正在更新的对象 - 在提交时,您可以通过 @Output 发出此对象,或者使用适当的服务(可能是更好的方法)