项目

本文档有多个版本。请选择最适合您的选项。

UI
Database

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 导入了 FormGroupFormBuilderValidators
  • 添加了一个 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();
    });
  }
}
  • 导入了 NgbDateNativeAdapterNgbDateAdapter
  • 我们添加了一个新的提供者 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>

最终的操作下拉用户界面如下所示:

bookstore-final-actions-dropdown

单击“删除”操作将调用 delete 方法,然后显示确认弹窗,如下所示:

bookstore-confirmation-popup


在本文档中