Web應用程序開發教程 - 第三章: 創建,更新和刪除圖書
關於本教程
在本系列教程中, 你將構建一個名為 Acme.BookStore
的用於管理書籍及其作者列表的基於ABP的應用程序. 它是使用以下技術開發的:
- {{DB_Text}} 做為ORM提供程序.
- {{UI_Value}} 做為UI框架.
本教程分為以下部分:
- Part 1: 創建服務端
- Part 2: 圖書列表頁面
- Part 3: 創建,更新和刪除圖書(本章)
- Part 4: 集成測試
- Part 5: 授權
- Part 6: 作者: 領域層
- Part 7: 作者: 數據庫集成
- Part 8: 作者: 應用服務層
- Part 9: 作者: 用戶頁面
- Part 10: 圖書到作者的關系
下載源碼
本教程根據你的UI 和 Database偏好有多個版,我們准備了兩種可供下載的源碼組合:
{{if UI == "MVC"}}
創建新書籍
通過本節, 你將會了解如何創建一個 modal form 來實現新增書籍的功能. 最終成果如下圖所示:
創建 modal form
在 Acme.BookStore.Web
項目的 Pages/Books
目錄下新建一個 CreateModal.cshtml
Razor頁面:
CreateModal.cshtml.cs
打開 CreateModal.cshtml.cs
代碼文件,用如下代碼替換 CreateModalModel
類的實現:
using System.Threading.Tasks;
using Acme.BookStore.Books;
using Microsoft.AspNetCore.Mvc;
namespace Acme.BookStore.Web.Pages.Books
{
public class CreateModalModel : BookStorePageModel
{
[BindProperty]
public CreateUpdateBookDto Book { get; set; }
private readonly IBookAppService _bookAppService;
public CreateModalModel(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public void OnGet()
{
Book = new CreateUpdateBookDto();
}
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.CreateAsync(Book);
return NoContent();
}
}
}
- 該類派生於
BookStorePageModel
而非默認的PageModel
.BookStorePageModel
繼承了PageModel
並且添加了一些可以被你的page model類使用的通用屬性和方法. Book
屬性上的[BindProperty]
特性將post請求提交上來的數據綁定到該屬性上.- 該類通過構造函數注入了
IBookAppService
應用服務,並且在OnPostAsync
處理程序中調用了服務的CreateAsync
方法. - 它在
OnGet
方法中創建一個新的CreateUpdateBookDto
對象。 ASP.NET Core不需要像這樣創建一個新實例就可以正常工作. 但是它不會為你創建實例,並且如果你的類在類構造函數中具有一些默認值分配或代碼執行,它們將無法工作. 對於這種情況,我們為某些CreateUpdateBookDto
屬性設置了默認值.
CreateModal.cshtml
打開 CreateModal.cshtml
文件並粘貼如下代碼:
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model CreateModalModel
@inject IStringLocalizer<BookStoreResource> L
@{
Layout = null;
}
<abp-dynamic-form abp-model="Book" asp-page="/Books/CreateModal">
<abp-modal>
<abp-modal-header title="@L["NewBook"].Value"></abp-modal-header>
<abp-modal-body>
<abp-form-content />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
- 這個 modal 使用
abp-dynamic-form
tag Helper 根據CreateBookViewModel
類自動構建了表單. abp-model
指定了Book
屬性為模型對象.data-ajaxForm
設置了表單通過AJAX提交,而不是經典的頁面回發.abp-form-content
tag helper 作為表單控件渲染位置的占位符 (這是可選的,只有你在abp-dynamic-form
中像本示例這樣添加了其他內容才需要).
提示: 就像在本示例中一樣,
Layout
應該為null
,因為當通過AJAX加載模態時,我們不希望包括所有布局.
添加 "New book" 按鈕
打開 Pages/Books/Index.cshtml
並按如下代碼修改 abp-card-header
:
<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<abp-card-title>@L["Books"]</abp-card-title>
</abp-column>
<abp-column size-md="_6" class="text-right">
<abp-button id="NewBookButton"
text="@L["NewBook"].Value"
icon="plus"
button-type="Primary"/>
</abp-column>
</abp-row>
</abp-card-header>
Index.cshtml
的內容最終如下所示:
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@model IndexModel
@inject IStringLocalizer<BookStoreResource> L
@section scripts
{
<abp-script src="/Pages/Books/Index.js"/>
}
<abp-card>
<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<abp-card-title>@L["Books"]</abp-card-title>
</abp-column>
<abp-column size-md="_6" class="text-right">
<abp-button id="NewBookButton"
text="@L["NewBook"].Value"
icon="plus"
button-type="Primary"/>
</abp-column>
</abp-row>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="BooksTable"></abp-table>
</abp-card-body>
</abp-card>
如下圖所示,只是在表格 右上方 添加了 New book 按鈕:
打開 Pages/book/index.js
在 datatable
配置代碼后面添加如下代碼:
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
createModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
abp.ModalManager
是一個在客戶端打開和管理modal的輔助類.它基於Twitter Bootstrap的標准modal組件通過簡化的API抽象隱藏了許多細節.createModal.onResult(...)
用於在創建書籍后刷新數據表格.createModal.open();
用於打開模態創建新書籍.
Index.js
的內容最終如下所示:
$(function () {
var l = abp.localization.getResource('BookStore');
var dataTable = $('#BooksTable').DataTable(
abp.libs.datatables.normalizeConfiguration({
serverSide: true,
paging: true,
order: [[1, "asc"]],
searching: false,
scrollX: true,
ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList),
columnDefs: [
{
title: l('Name'),
data: "name"
},
{
title: l('Type'),
data: "type",
render: function (data) {
return l('Enum:BookType:' + data);
}
},
{
title: l('PublishDate'),
data: "publishDate",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString();
}
},
{
title: l('Price'),
data: "price"
},
{
title: l('CreationTime'), data: "creationTime",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString(luxon.DateTime.DATETIME_SHORT);
}
}
]
})
);
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
createModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
現在,你可以 運行程序 通過新的 modal form 來創建書籍了.
更新書籍
在 Acme.BookStore.Web
項目的 Pages/Books
目錄下新建一個名叫 EditModal.cshtml
的Razor頁面:
EditModal.cshtml.cs
打開 EditModal.cshtml.cs
文件(EditModalModel
類) 並替換成以下代碼:
using System;
using System.Threading.Tasks;
using Acme.BookStore.Books;
using Microsoft.AspNetCore.Mvc;
namespace Acme.BookStore.Web.Pages.Books
{
public class EditModalModel : BookStorePageModel
{
[HiddenInput]
[BindProperty(SupportsGet = true)]
public Guid Id { get; set; }
[BindProperty]
public CreateUpdateBookDto Book { get; set; }
private readonly IBookAppService _bookAppService;
public EditModalModel(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
public async Task OnGetAsync()
{
var bookDto = await _bookAppService.GetAsync(Id);
Book = ObjectMapper.Map<BookDto, CreateUpdateBookDto>(bookDto);
}
public async Task<IActionResult> OnPostAsync()
{
await _bookAppService.UpdateAsync(Id, Book);
return NoContent();
}
}
}
[HiddenInput]
和[BindProperty]
是標准的 ASP.NET Core MVC 特性.這里啟用SupportsGet
從Http請求的查詢字符串中獲取Id的值.- 在
OnGetAsync
方法中,將BookAppService.GetAsync
方法返回的BookDto
映射成CreateUpdateBookDto
並賦值給Book屬性. OnPostAsync
方法直接使用BookAppService.UpdateAsync
來更新實體.
BookDto 到 CreateUpdateBookDto 對象映射
為了執行 BookDto
到 CreateUpdateBookDto
對象映射,請打開 Acme.BookStore.Web
項目中的 BookStoreWebAutoMapperProfile.cs
並更改它,如下所示:
using AutoMapper;
namespace Acme.BookStore.Web
{
public class BookStoreWebAutoMapperProfile : Profile
{
public BookStoreWebAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
}
}
}
- 我們添加了
CreateMap<BookDto, CreateUpdateBookDto>();
作為映射定義.
請注意,我們在Web層中進行映射定義是一種最佳實踐,因為僅在該層中需要它.
EditModal.cshtml
將 EditModal.cshtml
頁面內容替換成如下代碼:
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EditModalModel
@inject IStringLocalizer<BookStoreResource> L
@{
Layout = null;
}
<abp-dynamic-form abp-model="Book" asp-page="/Books/EditModal">
<abp-modal>
<abp-modal-header title="@L["Update"].Value"></abp-modal-header>
<abp-modal-body>
<abp-input asp-for="Id" />
<abp-form-content />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
這個頁面內容和 CreateModal.cshtml
非常相似,除了以下幾點:
- 它包含
id
屬性的abp-input
, 用於存儲編輯書的id
(它是隱藏的Input) - 此頁面指定的post地址是
Books/EditModal
.
為表格添加 "操作(Actions)" 下拉菜單
我們將為表格每行添加下拉按鈕 ("Actions"):
打開 Pages/Books/Index.cshtml
頁面,並按下方所示修改表格部分的代碼:
$(function () {
var l = abp.localization.getResource('BookStore');
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal');
var dataTable = $('#BooksTable').DataTable(
abp.libs.datatables.normalizeConfiguration({
serverSide: true,
paging: true,
order: [[1, "asc"]],
searching: false,
scrollX: true,
ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList),
columnDefs: [
{
title: l('Actions'),
rowAction: {
items:
[
{
text: l('Edit'),
action: function (data) {
editModal.open({ id: data.record.id });
}
}
]
}
},
{
title: l('Name'),
data: "name"
},
{
title: l('Type'),
data: "type",
render: function (data) {
return l('Enum:BookType:' + data);
}
},
{
title: l('PublishDate'),
data: "publishDate",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString();
}
},
{
title: l('Price'),
data: "price"
},
{
title: l('CreationTime'), data: "creationTime",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString(luxon.DateTime.DATETIME_SHORT);
}
}
]
})
);
createModal.onResult(function () {
dataTable.ajax.reload();
});
editModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
- 增加了一個新的
ModalManager
名為editModal
打開編輯模態框. - 在
columnDefs
部分的開頭添加了一個新列,用於"Actions"下拉按鈕. - "Edit" 動作簡單地調用
editModal.open()
打開編輯模態框. editModal.onResult(...)
當你關閉編程模態框時進行回調刷新數據表格.
你可以運行應用程序,並通過選擇一本書的編輯操作編輯任何一本書.
最終的UI看起來如下:
刪除書籍
打開 Pages/book/index.js
文件,在 rowAction
items
下新增一項:
{
text: l('Delete'),
confirmMessage: function (data) {
return l('BookDeletionConfirmationMessage', data.record.name);
},
action: function (data) {
acme.bookStore.books.book
.delete(data.record.id)
.then(function() {
abp.notify.info(l('SuccessfullyDeleted'));
dataTable.ajax.reload();
});
}
}
confirmMessage
用來在實際執行action
之前向用戶進行確認.- 通過javascript代理方法
acme.bookStore.books.book.delete(...)
執行一個AJAX請求來刪除一個book實體. abp.notify.info
用來在執行刪除操作后顯示一個toastr通知信息.
由於我們使用了兩個新的本地化文本(BookDeletionConfirmationMessage
和SuccesslyDeleted
),因此你需要將它們添加到本地化文件(Acme.BookStore.Domain.Shared
項目的Localization/BookStore
文件夾下的en.json
):
"BookDeletionConfirmationMessage": "Are you sure to delete the book '{0}'?",
"SuccessfullyDeleted": "Successfully deleted!"
Index.js
的內容最終如下所示:
$(function () {
var l = abp.localization.getResource('BookStore');
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal');
var dataTable = $('#BooksTable').DataTable(
abp.libs.datatables.normalizeConfiguration({
serverSide: true,
paging: true,
order: [[1, "asc"]],
searching: false,
scrollX: true,
ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList),
columnDefs: [
{
title: l('Actions'),
rowAction: {
items:
[
{
text: l('Edit'),
action: function (data) {
editModal.open({ id: data.record.id });
}
},
{
text: l('Delete'),
confirmMessage: function (data) {
return l(
'BookDeletionConfirmationMessage',
data.record.name
);
},
action: function (data) {
acme.bookStore.books.book
.delete(data.record.id)
.then(function() {
abp.notify.info(
l('SuccessfullyDeleted')
);
dataTable.ajax.reload();
});
}
}
]
}
},
{
title: l('Name'),
data: "name"
},
{
title: l('Type'),
data: "type",
render: function (data) {
return l('Enum:BookType:' + data);
}
},
{
title: l('PublishDate'),
data: "publishDate",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString();
}
},
{
title: l('Price'),
data: "price"
},
{
title: l('CreationTime'), data: "creationTime",
render: function (data) {
return luxon
.DateTime
.fromISO(data, {
locale: abp.localization.currentCulture.name
}).toLocaleString(luxon.DateTime.DATETIME_SHORT);
}
}
]
})
);
createModal.onResult(function () {
dataTable.ajax.reload();
});
editModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewBookButton').click(function (e) {
e.preventDefault();
createModal.open();
});
});
你可以運行程序並嘗試刪除一本書.
{{end}}
{{if UI == "NG"}}
創建新書籍
下面的章節中,你將學習到如何創建一個新的模態對話框來新增書籍.
BookComponent
打開 /src/app/book/book.component.ts
使用以下內容替換:
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto } from './models';
import { BookService } from './services';
@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; // add this line
constructor(public readonly list: ListService, private bookService: BookService) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
});
}
// add new method
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-right col col-md-6">
<!-- Add the "new book" button here -->
<div class="text-lg-right pt-2">
<button id="create" class="btn btn-primary" type="button" (click)="createBook()">
<i class="fa fa-plus mr-1"></i>
<span>{%{{{ "::NewBook" | abpLocalization }}}%}</span>
</button>
</div>
</div>
</div>
</div>
<div class="card-body">
<!-- ngx-datatable should be here! -->
</div>
</div>
<!-- Add the modal here -->
<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>
- 添加了
New book
按鈕到卡片頭部. - 添加了
abp-modal
渲染模態框,允許用戶創建新書.abp-modal
是顯示模態框的預構建組件. 你也可以使用其它方法顯示模態框,但abp-modal
提供了一些附加的好處.
你可以打開瀏覽器,點擊New book按鈕看到模態框.
添加響應式表單
響應式表單 提供一種模型驅動的方法來處理其值隨時間變化的表單輸入.
打開 /src/app/book/book.component.ts
使用以下內容替換:
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto, BookType } from './models'; // add BookType
import { BookService } from './services';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this
@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; // add this line
bookType = BookType; // add this line
// add bookTypes as a list of BookType enum members
bookTypes = Object.keys(this.bookType).filter(
(key) => typeof this.bookType[key] === 'number'
);
isModalOpen = false;
constructor(
public readonly list: ListService,
private bookService: BookService,
private fb: FormBuilder // inject FormBuilder
) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
});
}
createBook() {
this.buildForm(); // add this line
this.isModalOpen = true;
}
// add buildForm method
buildForm() {
this.form = this.fb.group({
name: ['', Validators.required],
type: [null, Validators.required],
publishDate: [null, Validators.required],
price: [null, Validators.required],
});
}
// add save method
save() {
if (this.form.invalid) {
return;
}
this.bookService.createByInput(this.form.value).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
}
}
- 導入了
FormGroup, FormBuilder and Validators
. - 添加了
form: FormGroup
變量. - 添加了
bookType
屬性,你可以從模板中獲取BookType
枚舉成員. - 添加了
bookTypes
屬性作為BookType
枚舉成員列表. 將在表單選項中使用. - 我們注入了
fb: 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="form-group">
<label for="book-name">Name</label><span> * </span>
<input type="text" id="book-name" class="form-control" formControlName="name" autofocus />
</div>
<div class="form-group">
<label for="book-price">Price</label><span> * </span>
<input type="number" id="book-price" class="form-control" formControlName="price" />
</div>
<div class="form-group">
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="bookType[type]" *ngFor="let type of bookTypes"> {%{{{ type }}}%}</option>
</select>
</div>
<div class="form-group">
<label>Publish date</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>
<!--added save button-->
<button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
<i class="fa fa-check mr-1"></i>
{%{{{ '::Save' | abpLocalization }}}%}
</button>
</ng-template>
Datepicker
我們在這個組件中使用了NgBootstrap datepicker. 因此需要添加與此組件相關的依賴項.
打開 /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'; // add this line
@NgModule({
declarations: [BookComponent],
imports: [
BookRoutingModule,
SharedModule,
NgbDatepickerModule, // add this line
]
})
export class BookModule { }
- 我們導入了
NgbDatepickerModule
來使用日期選擇器.
打開 /src/app/book/book.component.ts
使用以內內容替換:
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto, BookType } from './models';
import { BookService } from './services';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
// added this line
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 } // add this line
],
})
export class BookComponent implements OnInit {
book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
form: FormGroup;
bookType = BookType;
bookTypes = Object.keys(this.bookType).filter(
(key) => typeof this.bookType[key] === 'number'
);
isModalOpen = false;
constructor(
public readonly list: ListService,
private bookService: BookService,
private fb: FormBuilder
) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(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.createByInput(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 } from '@angular/core';
import { BookDto, BookType, CreateUpdateBookDto } from './models';
import { BookService } from './services';
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 = new BookDto(); // declare selectedBook
form: FormGroup;
bookType = BookType;
bookTypes = Object.keys(this.bookType).filter(
(key) => typeof this.bookType[key] === 'number'
);
isModalOpen = false;
constructor(
public readonly list: ListService,
private bookService: BookService,
private fb: FormBuilder
) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
});
}
createBook() {
this.selectedBook = new BookDto(); // reset the selected book
this.buildForm();
this.isModalOpen = true;
}
// Add editBook method
editBook(id: string) {
this.bookService.getById(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],
});
}
// change the save method
save() {
if (this.form.invalid) {
return;
}
const request = this.selectedBook.id
? this.bookService.updateByIdAndInput(this.form.value, this.selectedBook.id)
: this.bookService.createByInput(this.form.value);
request.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
}
}
- 我們聲明了類型為
BookDto
的selectedBook
變量. - 我們添加了
editBook
方法, 根據給定圖書Id
設置selectedBook
對象. - 我們替換了
buildForm
方法使用selectedBook
數據創建表單. - 我們替換了
createBook
方法,設置selectedBook
為空對象. - 我們替換了
save
方法.
添加 "Actions" 下拉框到表格
打開 /src/app/book/book.component.html
在 ngx-datatable
第一列添加 ngx-datatable-column
定義:
<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 mr-1"></i>{%{{{ '::Actions' | abpLocalization }}}%}
</button>
<div ngbDropdownMenu>
<button ngbDropdownItem (click)="editBook(row.id)">
{%{{{ '::Edit' | abpLocalization }}}%}
</button>
</div>
</div>
</ng-template>
</ngx-datatable-column>
在表格的第一列添加了一個 "Actions" 下拉菜單,如下圖所示:
同時如下所示更改 ng-template #abpHeader
部分:
<ng-template #abpHeader>
<h3>{%{{{ (selectedBook.id ? '::Edit' : '::NewBook' ) | abpLocalization }}}%}</h3>
</ng-template>
模板將在標題中顯示 Edit 文本用於編輯記錄操作, New Book 用於添加新記錄操作.
刪除書籍
打開 /src/app/book/book.component.ts
注入 ConfirmationService
.
所示替換構造函數:
// ...
// add new imports
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared';
//change the constructor
constructor(
public readonly list: ListService,
private bookService: BookService,
private fb: FormBuilder,
private confirmation: ConfirmationService // inject the ConfirmationService
) {}
// Add a delete method
delete(id: string) {
this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure').subscribe((status) => {
if (status === Confirmation.Status.confirm) {
this.bookService.deleteById(id).subscribe(() => this.list.get());
}
});
}
- 我們注入了
ConfirmationService
. - 我們注入了
ConfirmationService
到構造函數. - 添加了
delete
方法.
參閱確認彈層文檔了解該服務的更多信息.
添加刪除按鈕:
打開 /src/app/book/book.component.html
修改 ngbDropdownMenu
添加刪除按鈕:
<div ngbDropdownMenu>
<!-- add the Delete button -->
<button ngbDropdownItem (click)="delete(row.id)">
{%{{{ '::Delete' | abpLocalization }}}%}
</button>
</div>
最終操作下拉框UI看起來如下:
點擊 delete
動作調用 delete
方法,然后無法顯示一個確認彈層如下圖所示.
{{end}}
下一章
查看本教程的下一章.