Web 应用开发教程 - 第 3 部分:创建、更新和删除书籍
创建新书
在本节中,你将学习如何创建一个新的模态对话框表单来创建新书。
BookComponent
打开 /src/app/book/book.component.ts 并按如下所示替换内容:
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit, inject } from '@angular/core';
import { BookService, BookDto } from '@proxy/books';
@Component({
selector: 'app-book',
templateUrl: './book.component.html',
styleUrls: ['./book.component.scss'],
providers: [ListService],
})
export class BookComponent implements OnInit {
book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
isModalOpen = false;
public readonly list = inject(ListService);
private readonly bookService = inject(BookService);
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
});
}
//添加新方法
createBook() {
this.isModalOpen = true;
}
}
- 我们定义了一个名为
isModalOpen的属性和一个名为createBook的方法。
打开 /src/app/book/book.component.html 并进行以下更改:
<div class="card">
<div class="card-header">
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">{{ '::Menu:Books' | abpLocalization }}</h5>
</div>
<div class="text-end col col-md-6">
<!-- 在此处添加“新建图书”按钮 -->
<div class="text-lg-end pt-2">
<button id="create" class="btn btn-primary" type="button" (click)="createBook()">
<i class="fa fa-plus me-1"></i>
<span>{{ "::NewBook" | abpLocalization }}</span>
</button>
</div>
</div>
</div>
</div>
<div class="card-body">
<!-- ngx-datatable 应在此处! -->
</div>
</div>
<!-- 在此处添加模态框 -->
<abp-modal [(visible)]="isModalOpen">
<ng-template #abpHeader>
<h3>{{ '::NewBook' | abpLocalization }}</h3>
</ng-template>
<ng-template #abpBody> </ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" abpClose>
{{ '::Close' | abpLocalization }}
</button>
</ng-template>
</abp-modal>
- 在卡片头部添加了一个
新建图书按钮。 - 添加了
abp-modal,它渲染一个模态框以允许用户创建新书。abp-modal是一个用于显示模态框的预构建组件。虽然你可以使用其他方法来显示模态框,但abp-modal提供了额外的优势。
你可以打开浏览器并单击新建图书按钮以查看新的模态框。
创建响应式表单
响应式表单提供了一种模型驱动的方法来处理值随时间变化的表单输入。
打开 /src/app/book/book.component.ts 并按如下所示替换内容:
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit, inject } from '@angular/core';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; // 添加 bookTypeOptions
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // 添加这行
@Component({
selector: 'app-book',
templateUrl: './book.component.html',
styleUrls: ['./book.component.scss'],
providers: [ListService],
})
export class BookComponent implements OnInit {
book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
form: FormGroup; // 添加这行
// 将 bookTypes 作为 BookType 枚举成员的列表添加
bookTypes = bookTypeOptions;
isModalOpen = false;
public readonly list = inject(ListService);
private readonly bookService = inject(BookService);
private readonly fb = inject(FormBuilder); // 注入 FormBuilder
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
});
}
// 添加新方法
createBook() {
this.buildForm(); // 添加这行
this.isModalOpen = true;
}
// 添加 buildForm 方法
buildForm() {
this.form = this.fb.group({
name: ['', Validators.required],
type: [null, Validators.required],
publishDate: [null, Validators.required],
price: [null, Validators.required],
});
}
// 添加 save 方法
save() {
if (this.form.invalid) {
return;
}
this.bookService.create(this.form.value).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
}
}
- 从
@angular/forms导入了FormGroup、FormBuilder和Validators。 - 添加了一个
form: FormGroup属性。 - 添加了一个
bookTypes属性作为BookType枚举成员的列表。这将在表单选项中使用。 - 使用
FormBuilder注入函数进行注入。FormBuilder 提供了生成表单控件的便捷方法。它减少了构建复杂表单所需的样板代码。 - 在文件末尾添加了一个
buildForm方法,并在createBook方法中执行了buildForm()。 - 添加了一个
save方法。
打开 /src/app/book/book.component.html 并将 <ng-template #abpBody> </ng-template> 替换为以下代码部分:
<ng-template #abpBody>
<form [formGroup]="form" (ngSubmit)="save()">
<div class="mt-2">
<label for="book-name">名称</label><span> * </span>
<input type="text" id="book-name" class="form-control" formControlName="name" autofocus />
</div>
<div class="mt-2">
<label for="book-price">价格</label><span> * </span>
<input type="number" id="book-price" class="form-control" formControlName="price" />
</div>
<div class="mt-2">
<label for="book-type">类型</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">选择书籍类型</option>
<option [ngValue]="type.value" *ngFor="let type of bookTypes"> {{ '::Enum:BookType.' + type.value | abpLocalization }}</option>
</select>
</div>
<div class="mt-2">
<label>出版日期</label><span> * </span>
<input
#datepicker="ngbDatepicker"
class="form-control"
name="datepicker"
formControlName="publishDate"
ngbDatepicker
(click)="datepicker.toggle()"
/>
</div>
</form>
</ng-template>
同时,将 <ng-template #abpFooter> </ng-template> 替换为以下代码部分:
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" abpClose>
{{ '::Close' | abpLocalization }}
</button>
<!--添加保存按钮-->
<button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
<i class="fa fa-check mr-1"></i>
{{ '::Save' | abpLocalization }}
</button>
</ng-template>
日期选择器
我们在此组件中使用了 NgBootstrap 日期选择器。因此,我们需要安排与此组件相关的依赖项。
打开 /src/app/book/book.module.ts 并按如下所示替换内容:
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { BookRoutingModule } from './book-routing.module';
import { BookComponent } from './book.component';
import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; // 添加这行
@NgModule({
declarations: [BookComponent],
imports: [
BookRoutingModule,
SharedModule,
NgbDatepickerModule, // 添加这行
]
})
export class BookModule { }
- 我们导入了
NgbDatepickerModule以便能够使用日期选择器。
打开 /src/app/book/book.component.ts 并按如下所示替换内容:
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit, inject } from '@angular/core';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
// 添加了这行
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-book',
templateUrl: './book.component.html',
styleUrls: ['./book.component.scss'],
providers: [
ListService,
{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter } // 添加这行
],
})
export class BookComponent implements OnInit {
book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
form: FormGroup;
bookTypes = bookTypeOptions;
isModalOpen = false;
public readonly list = inject(ListService);
private readonly bookService = inject(BookService);
private readonly fb = inject(FormBuilder);
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
});
}
createBook() {
this.buildForm();
this.isModalOpen = true;
}
buildForm() {
this.form = this.fb.group({
name: ['', Validators.required],
type: [null, Validators.required],
publishDate: [null, Validators.required],
price: [null, Validators.required],
});
}
save() {
if (this.form.invalid) {
return;
}
this.bookService.create(this.form.value).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
}
}
- 导入了
NgbDateNativeAdapter和NgbDateAdapter。 - 我们添加了一个新的提供者
NgbDateAdapter,它将 Datepicker 值转换为Date类型。有关更多详细信息,请查看 datepicker adapters。
现在,你可以打开浏览器查看更改:
更新书籍
打开 /src/app/book/book.component.ts 并按如下所示替换内容:
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit, inject } from '@angular/core';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-book',
templateUrl: './book.component.html',
styleUrls: ['./book.component.scss'],
providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookComponent implements OnInit {
book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
selectedBook = {} as BookDto; // 声明 selectedBook
form: FormGroup;
bookTypes = bookTypeOptions;
isModalOpen = false;
public readonly list = inject(ListService);
private readonly bookService = inject(BookService);
private readonly fb = inject(FormBuilder);
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
});
}
createBook() {
this.selectedBook = {} as BookDto; // 重置选中的书籍
this.buildForm();
this.isModalOpen = true;
}
// 添加 editBook 方法
editBook(id: string) {
this.bookService.get(id).subscribe((book) => {
this.selectedBook = book;
this.buildForm();
this.isModalOpen = true;
});
}
buildForm() {
this.form = this.fb.group({
name: [this.selectedBook.name || '', Validators.required],
type: [this.selectedBook.type || null, Validators.required],
publishDate: [
this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null,
Validators.required,
],
price: [this.selectedBook.price || null, Validators.required],
});
}
// 更改 save 方法
save() {
if (this.form.invalid) {
return;
}
const request = this.selectedBook.id
? this.bookService.update(this.selectedBook.id, this.form.value)
: this.bookService.create(this.form.value);
request.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
}
}
- 我们声明了一个名为
selectedBook的变量,类型为BookDto。 - 我们添加了一个
editBook方法。该方法获取给定id的书籍并将其设置为selectedBook对象。 - 我们替换了
buildForm方法,使其使用selectedBook数据创建表单。 - 我们替换了
createBook方法,使其将selectedBook设置为空对象。 - 我们更改了
save方法以处理创建和更新操作。
向表格添加“操作”下拉菜单
打开 /src/app/book/book.component.html 并将以下 ngx-datatable-column 定义添加为 ngx-datatable 中的第一列:
<ngx-datatable-column
[name]="'::Actions' | abpLocalization"
[maxWidth]="150"
[sortable]="false"
>
<ng-template let-row="row" ngx-datatable-cell-template>
<div ngbDropdown container="body" class="d-inline-block">
<button
class="btn btn-primary btn-sm dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
ngbDropdownToggle
>
<i class="fa fa-cog me-1"></i>{{ '::Actions' | abpLocalization }}
</button>
<div ngbDropdownMenu>
<button ngbDropdownItem (click)="editBook(row.id)">
{{ '::Edit' | abpLocalization }}
</button>
</div>
</div>
</ng-template>
</ngx-datatable-column>
添加了一个“操作”下拉菜单作为表格的第一列,如下所示:
同时,按如下所示更改 ng-template #abpHeader 部分:
<ng-template #abpHeader>
<h3>{{ (selectedBook.id ? '::Edit' : '::NewBook' ) | abpLocalization }}</h3>
</ng-template>
此模板将在标题中为编辑记录操作显示编辑文本,为新记录操作显示新建图书。
删除书籍
打开 /src/app/book/book.component.ts 文件并注入 ConfirmationService。
按如下所示替换注入的服务:
// ...
// 添加新的导入
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared';
import { Component, OnInit, inject } from '@angular/core';
// ...
export class BookComponent implements OnInit {
// ...
public readonly list = inject(ListService);
private readonly bookService = inject(BookService);
private readonly fb = inject(FormBuilder);
private readonly confirmation = inject(ConfirmationService); // 注入 ConfirmationService
// ...
// 添加一个 delete 方法
delete(id: string) {
this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure').subscribe((status) => {
if (status === Confirmation.Status.confirm) {
this.bookService.delete(id).subscribe(() => this.list.get());
}
});
}
}
- 我们导入了
ConfirmationService。 - 我们使用
inject()函数注入了ConfirmationService。 - 添加了一个
delete方法。
有关此服务的更多信息,请查看 确认弹窗文档。
添加删除按钮
打开 /src/app/book/book.component.html 并修改 ngbDropdownMenu 以添加删除按钮,如下所示:
<div ngbDropdownMenu>
<!-- 添加删除按钮 -->
<button ngbDropdownItem (click)="delete(row.id)">
{{ '::Delete' | abpLocalization }}
</button>
</div>
最终的操作下拉用户界面如下所示:
单击“删除”操作将调用 delete 方法,然后显示确认弹窗,如下所示:
抠丁客







