"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
发出此对象,或者使用适当的服务(可能是更好的方法)
我正在从事一个 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
发出此对象,或者使用适当的服务(可能是更好的方法)