Angular8教程

Angular8 工作示例

在这里,我们将学习有关 Angula 的完整分步工作示例8.
让我们创建一个 Angular 应用程序来检查我们的日常开支。让我们选择 ExpenseManager 作为我们新应用程序的选择。

创建应用程序

使用以下命令创建新应用程序。
cd /path/to/workspace
ng new expense-manager
这里,
new 是 ng CLI 应用程序的命令之一。它将用于创建新应用程序。它将询问一些基本问题以创建新应用程序。让应用程序选择默认选项就足够了。对于下面提到的路由问题,请指定
Would you like to add Angular routing? No
一旦回答了基本问题,ng CLI 应用程序就会在费用管理器文件夹下创建一个新的 Angular 应用程序。
让我们进入我们新创建的应用程序文件夹。
cd expense-manager
让我们使用以下命令启动应用程序。
ng serve
让我们启动浏览器并打开 http://localhost:4200。浏览器将显示如下所示的应用程序-
applications
让我们更改应用程序的标题以更好地反映我们的应用程序。打开 src/app/app.component.ts 并更改如下指定的代码-
export class AppComponent { 
   title = 'Expense Manager';
}
我们的最终应用程序将在浏览器中呈现,如下所示-
applications

添加组件

使用 ng generate component 命令创建一个新组件,如下所示-
ng generate component expense-entry

输出

输出如下-
CREATE src/app/expense-entry/expense-entry.component.html (28 bytes)
CREATE src/app/expense-entry/expense-entry.component.spec.ts (671 bytes)
CREATE src/app/expense-entry/expense-entry.component.ts (296 bytes)
CREATE src/app/expense-entry/expense-entry.component.css (0 bytes)
UPDATE src/app/app.module.ts (431 bytes)
这里,
ExpenseEntryComponent 在 src/app/expense-entry 文件夹下创建。 创建组件类、模板和样式表。 AppModule 更新了新组件。
将 title 属性添加到 ExpenseEntryComponent (src/app/expense-entry/expense-entry.component.ts) component.
import { Component, OnInit } from '@angular/core';
@Component({
   selector: 'app-expense-entry',
   templateUrl: './expense-entry.component.html',
   styleUrls: ['./expense-entry.component.css']
})
export class ExpenseEntryComponent implements OnInit {
   title: string;
   constructor() { }
   ngOnInit() {
      this.title = "Expense Entry"
   }
}
使用以下内容更新模板, src/app/expense-entry/expense-entry.component.html
<p>{{ title }}</p>
打开
src/app/app.component.html
并包含新创建的组件。
<h1>{{ title }}</h1>
<app-expense-entry></app-expense-entry>
这里,
app-expense-entry 是选择器值,它可以用作常规 HTML 标签。
应用程序的输出如下所示-
HTML 标签

包括引导程序

让我们使用 styles 选项将引导程序包含到我们的 ExpenseManager 应用程序中,并更改默认模板以使用引导程序组件。
打开命令提示符并转到 ExpenseManager 应用程序。
cd /go/to/expense-manager
使用以下命令安装 bootstrapJQuery
npm install--save bootstrap@4.5.0 jquery@3.5.1
这里,
我们已经安装了 JQuery,因为 bootstrap 广泛地将 jquery 用于高级组件。
选项 angular.json 并设置 bootstrap 和 jquery 库路径。
{ 
   "projects": { 
      "expense-manager": { 
         "architect": { 
            "build": {
               "builder":"@angular-devkit/build-angular:browser", "options": { 
                  "outputPath": "dist/expense-manager", 
                  "index": "src/index.html", 
                  "main": "src/main.ts", 
                  "polyfills": "src/polyfills.ts", 
                  "tsConfig": "tsconfig.app.json", 
                  "aot": false, 
                  "assets": [ 
                     "src/favicon.ico", 
                     "src/assets" 
                  ], 
                  "styles": [ 
                     "./node_modules/bootstrap/dist/css/bootstrap.css", "src/styles.css" 
                  ], 
                  "scripts": [ 
                     "./node_modules/jquery/dist/jquery.js", "./node_modules/bootstrap/dist/js/bootstrap.js" 
                  ] 
               }, 
            }, 
         } 
   }}, 
   "defaultProject": "expense-manager" 
}
这里,
scripts 选项用于包含 JavaScript 库。通过 scripts 注册的 JavaScript 将可用于应用程序中的所有 Angular 组件。
打开 app.component.html 并按照以下指定更改内容
<!--Navigation--> 
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top"> 
   <div class="container"> 
      <a class="navbar-brand" href="#">{{ title }}</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"> 
         <span class="navbar-toggler-icon">
         </span> 
      </button> 
      <div class="collapse navbar-collapse" id="navbarResponsive"> 
         <ul class="navbar-nav ml-auto"> 
            <li class="nav-item active"> 
            <a class="nav-link" href="#">Home
               <span class="sr-only">(current)
               </span>
            </a> 
            </li> 
            <li class="nav-item"> 
            <a class="nav-link" href="#">Report</a> 
            </li> 
            <li class="nav-item"> 
            <a class="nav-link" href="#">Add Expense</a> 
            </li> 
            <li class="nav-item"> 
            <a class="nav-link" href="#">About</a> 
            </li> 
         </ul> 
      </div> 
   </div> 
</nav> 
<app-expense-entry></app-expense-entry>
这里,
使用引导程序导航和容器。
打开 src/app/expense-entry/expense-entry.component.html 并将其放在内容下方。
<!--Page Content--> 
<div class="container"> 
   <div class="row"> 
      <div class="col-lg-12 text-center" style="padding-top: 20px;"> 
         <div class="container" style="padding-left: 0px; padding-right: 0px;"> 
            <div class="row"> 
            <div class="col-sm" style="text-align: left;"> {{ title }} 
            </div> 
            <div class="col-sm" style="text-align: right;"> 
               <button type="button" class="btn btn-primary">Edit</button> 
            </div> 
            </div> 
         </div> 
         <div class="container box" style="margin-top: 10px;"> 
         <div class="row"> 
         <div class="col-2" style="text-align: right;">  
            <strong><em>Item:</em></strong> 
         </div> 
         <div class="col" style="text-align: left;"> 
            Pizza 
         </div>
         </div> 
         <div class="row"> 
         <div class="col-2" style="text-align: right;">
            <strong><em>Amount:</em></strong> 
         </div> 
         <div class="col" style="text-align: left;"> 
            20 
         </div> 
         </div> 
         <div class="row"> 
         <div class="col-2" style="text-align: right;"> 
            <strong><em>Category:</em></strong> 
         </div> 
         <div class="col" style="text-align: left;"> 
            Food 
         </div> 
         </div> 
         <div class="row"> 
         <div class="col-2" style="text-align: right;"> 
            <strong><em>Location:</em></strong>
         </div> 
         <div class="col" style="text-align: left;"> 
            Zomato 
         </div> 
         </div> 
         <div class="row"> 
         <div class="col-2" style="text-align: right;"> 
            <strong><em>Spend On:</em></strong> 
         </div> 
         <div class="col" style="text-align: left;"> 
            June 20, 2020 
         </div> 
         </div> 
      </div> 
   </div> 
</div> 
</div>
重新启动应用程序。
应用程序的输出如下-
重启标签
我们将在下一章改进应用程序以处理动态费用条目。

添加接口

创建 ExpenseEntry 接口 (src/app/expense-entry.ts) 并添加 id、amount、category、Location、spenseOn 和 createdOn。
export interface ExpenseEntry {
   id: number;
   item: string;
   amount: number;
   category: string;
   location: string;
   spendOn: Date;
   createdOn: Date;
}
ExpenseEntry 导入 ExpenseEntryComponent
import { ExpenseEntry } from '../expense-entry';
创建一个 ExpenseEntry 对象,费用条目如下图所示-
export class ExpenseEntryComponent implements OnInit {
   title: string;
   expenseEntry: ExpenseEntry;
   constructor() { }
   ngOnInit() {
      this.title = "Expense Entry";
      this.expenseEntry = {
         id: 1,
         item: "Pizza",
         amount: 21,
         category: "Food",
         location: "Zomato",
         spendOn: new Date(2020, 6, 1, 10, 10, 10),
         createdOn: new Date(2020, 6, 1, 10, 10, 10),
      };
   }
}
使用 expenseEntry 对象,src/app/expense-entry/expense-entry.component.html 更新组件模板,如下所示-
<!--Page Content-->
<div class="container">
   <div class="row">
      <div class="col-lg-12 text-center" style="padding-top: 20px;">
         <div class="container" style="padding-left: 0px; padding-right: 0px;">
            <div class="row">
               <div class="col-sm" style="text-align: left;">
                  {{ title }}
               </div>
               <div class="col-sm" style="text-align: right;">
                  <button type="button" class="btn btn-primary">Edit</button>
               </div>
            </div>
         </div>
         <div class="container box" style="margin-top: 10px;">
            <div class="row">
               <div class="col-2" style="text-align: right;">
                  <strong><em>Item:</em></strong>
               </div>
               <div class="col" style="text-align: left;">
                  {{ expenseEntry.item }} 
               </div>
            </div>
            <div class="row">
               <div class="col-2" style="text-align: right;">
                  <strong><em>Amount:</em></strong>
               </div>
               <div class="col" style="text-align: left;">
                  {{ expenseEntry.amount }}   
               </div>
            </div>
            <div class="row">
               <div class="col-2" style="text-align: right;">
                  <strong><em>Category:</em></strong>
               </div>
               <div class="col" style="text-align: left;">
                  {{ expenseEntry.category }} 
               </div>
            </div>
            <div class="row">
               <div class="col-2" style="text-align: right;">
                  <strong><em>Location:</em></strong>
               </div>
               <div class="col" style="text-align: left;">
                  {{ expenseEntry.location }} 
               </div>
            </div>
            <div class="row">
               <div class="col-2" style="text-align: right;">
                  <strong><em>Spend On:</em></strong>
               </div>
               <div class="col" style="text-align: left;">
                  {{ expenseEntry.spendOn }}  
               </div>
            </div>
         </div>
      </div>
   </div>
</div>
应用程序的输出如下-
接口

使用指令

让我们在 ExpenseManager 应用程序中添加一个新组件来列出费用条目。
打开命令提示符并转到项目根文件夹。
cd /go/to/expense-manager
启动应用程序。
ng serve
使用以下命令创建一个新组件 ExpenseEntryListComponent-
ng generate component ExpenseEntryList

输出

输出如下-
CREATE src/app/expense-entry-list/expense-entry-list.component.html (33 bytes) 
CREATE src/app/expense-entry-list/expense-entry-list.component.spec.ts (700 bytes) 
CREATE src/app/expense-entry-list/expense-entry-list.component.ts (315 bytes) 
CREATE src/app/expense-entry-list/expense-entry-list.component.css (0 bytes) 
UPDATE src/app/app.module.ts (548 bytes)
此处,该命令创建 ExpenseEntryList 组件并更新 AppModule 中的必要代码。
ExpenseEntry导入 ExpenseEntryListComponent组件 (src/app/expense-entry-list/expense-entry-list.component)
import { ExpenseEntry } from '../expense-entry';
ExpenseEntryListComponent (src/app/expense-entry-list/expense-entry-list.component) 中添加一个方法,getExpenseEntries() 以返回费用条目(模拟项目)列表)
getExpenseEntries() : ExpenseEntry[] { 
   let mockExpenseEntries : ExpenseEntry[] = [ 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "Mcdonald", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "KFC", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1,
         item: "Pizza",
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "Mcdonald", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "KFC", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) }, 
      { id: 1, 
         item: "Pizza", 
         amount: Math.floor((Math.random() * 10) + 1), 
         category: "Food", 
         location: "KFC", 
         spendOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10), 
         createdOn: new Date(2020, 4, Math.floor((Math.random() * 30) + 1), 10, 10, 10) 
      }, 
   ]; 
   return mockExpenseEntries; 
}
声明一个局部变量,费用条目并加载如下所述的费用条目的模拟列表-
title: string; 
expenseEntries: ExpenseEntry[]; 
constructor() { } 
ngOnInit() { 
   this.title = "Expense Entry List"; 
   this.expenseEntries = this.getExpenseEntries(); 
}
打开模板文件 (src/app/expense-entry-list/expense-entry-list.component.html) 并在表格中显示模拟条目。
<!--Page Content-->
<div class="container"> 
   <div class="row"> 
      <div class="col-lg-12 text-center" style="padding-top: 20px;">
         <div class="container" style="padding-left: 0px; padding-right: 0px;"> 
            <div class="row"> 
               <div class="col-sm" style="text-align: left;"> 
                  {{ title }} 
               </div> 
               <div class="col-sm" style="text-align: right;"> 
                  <button type="button" class="btn btn-primary">Edit</button> 
               </div> 
            </div> 
         </div> 
         <div class="container box" style="margin-top: 10px;"> 
            <table class="table table-striped"> 
               <thead> 
                  <tr> 
                     <th>Item</th> 
                     <th>Amount</th> 
                     <th>Category</th> 
                     <th>Location</th> 
                     <th>Spent On</th> 
                  </tr> 
               </thead> 
               <tbody> 
                  <tr *ngFor="let entry of expenseEntries"> 
                     <th scope="row">{{ entry.item }}</th> 
                     <th>{{ entry.amount }}</th> 
                     <td>{{ entry.category }}</td> 
                     <td>{{ entry.location }}</td> 
                     <td>{{ entry.spendOn | date: 'short' }}</td> 
                  </tr> 
               </tbody> 
            </table> 
         </div> 
      </div> 
   </div> 
</div>
这里,
使用过的引导表。 tabletable-striped 将根据 Boostrap 样式标准设置表格样式。 使用 ngFor 循环遍历 expenseEntries 并生成表格行。
打开 AppComponent 模板, src/app/app.component.html 并包含 ExpenseEntryListComponent 并删除 ExpenseEntryComponent如下图所示-
... 
<app-expense-entry-list></app-expense-entry-list>
最后,应用程序的输出如下所示。
AppComponent

使用管道

让我们在 ExpenseManager 应用程序中使用管道
打开 ExpenseEntryListComponent's 模板, src/app/expense-entry-list/expense-entry-list.component.html 并在 entry.spendOn 中包含管道 如下所述-
<td>{{ entry.spendOn | date: 'short' }}</td>
在这里,我们使用日期管道以短格式显示日期的支出。
最后,应用程序的输出如下所示-
Pipes

添加调试服务

运行以下命令以生成 Angular 服务 DebugService
ng g service debug
这将创建两个 Typescript 文件(调试服务及其测试),如下所示-
CREATE src/app/debug.service.spec.ts (328 bytes) 
CREATE src/app/debug.service.ts (134 bytes)
让我们分析 DebugService 服务的内容。
import { Injectable } from '@angular/core'; @Injectable({ 
   providedIn: 'root' 
}) 
export class DebugService { 
   constructor() { } 
}
这里,
@Injectable 装饰器附加到 DebugService 类,它使 DebugService 能够在应用程序的 Angular 组件中使用。 providerIn 选项及其值,root 允许在应用程序的所有组件中使用 DebugService。
让我们添加一个方法,Info,它将消息打印到浏览器控制台。
info(message : String) : void { 
   console.log(message); 
}
让我们在 ExpenseEntryListComponent 中初始化服务并使用它来打印消息。
import { Component, OnInit } from '@angular/core'; import { ExpenseEntry } from '../expense-entry'; import { DebugService } from '../debug.service'; @Component({ 
   selector: 'app-expense-entry-list', 
   templateUrl: './expense-entry-list.component.html', styleUrls: ['./expense-entry-list.component.css'] 
}) 
export class ExpenseEntryListComponent implements OnInit { 
   title: string; 
   expenseEntries: ExpenseEntry[]; 
   constructor(private debugService: DebugService) { } 
   ngOnInit() { 
      this.debugService.info("Expense Entry List 
      component initialized"); 
      this.title = "Expense Entry List"; 
      this.expenseEntries = this.getExpenseEntries(); 
   } 
   // other coding 
}
这里,
DebugService 使用构造函数参数初始化。设置 DebugService 类型的参数 (debugService) 将触发依赖注入以创建新的 DebugService 对象并将其设置到 ExpenseEntryListComponent 组件中。 在 ngOnInit 方法中调用 DebugService 的 info 方法会在浏览器控制台中打印消息。
可以使用开发者工具查看结果,如下图所示-
调试服务
让我们扩展应用程序以了解服务的范围。
让我们使用下面提到的命令创建一个 DebugComponent
ng generate component debug
CREATE src/app/debug/debug.component.html (20 bytes) CREATE src/app/debug/debug.component.spec.ts (621 bytes) 
CREATE src/app/debug/debug.component.ts (265 bytes) CREATE src/app/debug/debug.component.css (0 bytes) UPDATE src/app/app.module.ts (392 bytes)
让我们删除根模块中的 DebugService。
// src/app/debug.service.ts
import { Injectable } from '@angular/core'; @Injectable() 
export class DebugService { 
   constructor() { 
   }
   info(message : String) : void {     
      console.log(message); 
   } 
}
在 ExpenseEntryListComponent 组件下注册 DebugService。
// src/app/expense-entry-list/expense-entry-list.component.ts @Component({ 
   selector: 'app-expense-entry-list', 
   templateUrl: './expense-entry-list.component.html', 
   styleUrls: ['./expense-entry-list.component.css'] 
   providers: [DebugService] 
})
在这里,我们使用了提供者元数据 (ElementInjector) 来注册服务。
打开 DebugComponent (src/app/debug/debug.component.ts) 并导入 DebugService 并在组件的构造函数中设置一个实例。
import { Component, OnInit } from '@angular/core'; import { DebugService } from '../debug.service'; 
@Component({ 
   selector: 'app-debug', 
   templateUrl: './debug.component.html', 
   styleUrls: ['./debug.component.css'] 
}) 
export class DebugComponent implements OnInit { 
   constructor(private debugService: DebugService) { } 
   ngOnInit() { 
      this.debugService.info("Debug component gets service from Parent"); 
   } 
}
这里,我们没有注册 DebugService。因此,如果用作父组件,DebugService 将不可用。当在父组件内使用时,如果父组件有权访问服务,则该服务可从父组件获得。
打开 ExpenseEntryListComponent 模板 (src/app/expense-entry-list/expense-entry-list.component.html) 并包含如下所示的内容部分:
// existing content 
<app-debug></app-debug>
<ng-content></ng-content>
在这里,我们包含了一个内容部分和 DebugComponent 部分。
让我们将调试组件作为内容包含在 AppComponent 模板中的 ExpenseEntryListComponent 组件中。打开 AppComponent 模板并更改 app-expense-entry-list 如下-
// navigation code
<app-expense-entry-list>
<app-debug></app-debug>
</app-expense-entry-list>
此处,我们将 DebugComponent 作为内容包含在内。
让我们检查应用程序,它将在页面末尾显示 DebugService 模板,如下所示-
调试
另外,我们可以看到来自控制台中调试组件的两个调试信息。这表明调试组件从其父组件获取服务。
让我们更改在 ExpenseEntryListComponent 中注入服务的方式以及它如何影响服务的范围。将提供者注入器更改为 viewProviders 注入。 viewProviders 不会将服务注入到内容子项中,因此它应该会失败。
viewProviders: [DebugService]
检查应用程序,您将看到调试组件之一(用作内容子项)引发错误,如下所示-
应用程序
让我们移除模板中的调试组件并恢复应用程序。
打开 ExpenseEntryListComponent 模板 (src/app/expense-entry-list/expense-entry-list.component.html) 并删除以下内容
<app-debug></app-debug>
<ng-content></ng-content>
打开 AppComponent 模板并更改 app-expense-entry-list 如下-
// navigation code
<app-expense-entry-list>
</app-expense-entry-list>
ExpenseEntryListComponent 中的 viewProviders 设置更改为 providers
providers: [DebugService]
重新运行应用程序并检查结果。

创建费用服务

让我们在 ExpenseManager 应用程序中创建一个新服务 ExpenseEntryService 以与 Expense REST API 交互。 ExpenseEntryService 将获取最新的费用条目、插入新的费用条目、修改现有的费用条目并删除不需要的费用条目。
打开命令提示符并转到项目根文件夹。
cd /go/to/expense-manager
启动应用程序。
ng serve
运行以下命令以生成 Angular 服务 ExpenseService
ng generate service ExpenseEntry
这将创建两个 Typescript 文件(费用条目服务及其测试),如下所示-
CREATE src/app/expense-entry.service.spec.ts (364 bytes) 
CREATE src/app/expense-entry.service.ts (141 bytes)
打开 ExpenseEntryService (src/app/expense-entry.service.ts) 并从 rxjs 库中导入 ExpenseEntry、throwErrorcatchError 和从@angular/common/http 包中导入 HttpClient、HttpHeadersHttpErrorResponse
import { Injectable } from '@angular/core'; 
import { ExpenseEntry } from './expense-entry'; import { throwError } from 'rxjs';
import { catchError } from 'rxjs/operators'; 
import { HttpClient, HttpHeaders, HttpErrorResponse } from 
'@angular/common/http';
将 HttpClient 服务注入到我们的服务中。
constructor(private httpClient : HttpClient) { }
创建一个变量 expenseRestUrl 以指定 Expense Rest API 端点。
private expenseRestUrl = 'http://localhost:8000/api/expense';
创建一个变量, httpOptions 来设置 Http Header 选项。这将在 Angular HttpClient 服务调用 Http Rest API 期间使用。
private httpOptions = { 
   headers: new HttpHeaders( { 'Content-Type': 'application/json' }) 
};
完整代码如下-
import { Injectable } from '@angular/core';
import { ExpenseEntry } from './expense-entry';
import { Observable, throwError } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
@Injectable({
   providedIn: 'root'
})
export class ExpenseEntryService {
      private expenseRestUrl = 'api/expense';
      private httpOptions = {
         headers: new HttpHeaders( { 'Content-Type': 'application/json' })
      };
   constructor(
      private httpClient : HttpClient) { }
}

使用HttpClient服务进行Http编程

启动费用 REST API 应用程序,如下所示-
cd /go/to/expense-rest-api 
node .\server.js
ExpenseEntryService (src/app/expense-entry.service.ts) 服务中添加 getExpenseEntries()httpErrorHandler() 方法。
getExpenseEntries() : Observable<ExpenseEntry[]> {
   return this.httpClient.get<ExpenseEntry[]>(this.expenseRestUrl, this.httpOptions)
   .pipe(retry(3),catchError(this.httpErrorHandler));
}
getExpenseEntry(id: number) : Observable<ExpenseEntry> {
   return this.httpClient.get<ExpenseEntry>(this.expenseRestUrl + "/" + id, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}
private httpErrorHandler (error: HttpErrorResponse) {
      if (error.error instanceof ErrorEvent) {
      console.error("A client side error occurs. The error message is " + error.message);
      } else {
      console.error(
            "An error happened in server. The HTTP status code is "  + error.status + " and the error returned is " + error.message);
      }
      return throwError("Error occurred. Pleas try again");
}
这里,
getExpenseEntries() 使用费用端点调用 get() 方法并配置错误处理程序。此外,它还将 httpClient 配置为在失败时最多尝试 3 次。最后,它返回来自服务器的响应,类型为 (ExpenseEntry[]) Observable 对象。 getExpenseEntry 与 getExpenseEntries() 类似,不同之处在于它传递 ExpenseEntry 对象的 id 并获取 ExpenseEntry Observable 对象。
ExpenseEntryService 的完整代码如下-
import { Injectable } from '@angular/core';
import { ExpenseEntry } from './expense-entry';
import { Observable, throwError } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
@Injectable({
   providedIn: 'root'
})
export class ExpenseEntryService {
   private expenseRestUrl = 'http://localhost:8000/api/expense';
   private httpOptions = {
      headers: new HttpHeaders( { 'Content-Type': 'application/json' })
   };
   constructor(private httpClient : HttpClient) { } 
   getExpenseEntries() : Observable
    
   
    
      {
      
    return 
    this.httpClient.get
     
    
      (
     this.
     expenseRestUrl, this.httpOptions) .pipe( retry(3), catchError(
     this.
     httpErrorHandler) ); } getExpenseEntry(id: number) : Observable 
     
       { 
      return 
      this.httpClient.get 
      
        (
       this.
       expenseRestUrl + "/" + id, this.httpOptions) .pipe( retry(3), catchError(
       this.
       httpErrorHandler) ); } private httpErrorHandler (error: HttpErrorResponse) { 
       if (error.
       error instanceof ErrorEvent) { console.error(
       "A client side error occurs. The error message is " + error.message); } else { console.error( 
       "An error happened in server. The HTTP status code is " + error.status + " and the error returned is " + error.message); } return throwError(
       "Error occurred. Pleas try again"); } } 
       
      
    
    
   
打开 ExpenseEntryListComponent (src-entry-list-entry-list.component.ts) 并通过如下指定的构造函数注入 ExpenseEntryService
constructor(private debugService: DebugService, private restService : 
ExpenseEntryService ) { }
更改 getExpenseEntries() 函数。从 ExpenseEntryService 调用 getExpenseEntries() 方法,而不是返回模拟项。
getExpenseItems() {  
   this.restService.getExpenseEntries() 
      .subscribe( data =− this.expenseEntries = data ); 
}
完整的 ExpenseEntryListComponent 编码如下-
import { Component, OnInit } from '@angular/core';
import { ExpenseEntry } from '../expense-entry';
import { DebugService } from '../debug.service';
import { ExpenseEntryService } from '../expense-entry.service';
@Component({
   selector: 'app-expense-entry-list',
   templateUrl: './expense-entry-list.component.html',
   styleUrls: ['./expense-entry-list.component.css'],
   providers: [DebugService]
})
export class ExpenseEntryListComponent implements OnInit {
   title: string;
   expenseEntries: ExpenseEntry[];
   constructor(private debugService: DebugService, private restService : ExpenseEntryService ) { }
   ngOnInit() {
      this.debugService.info("Expense Entry List component initialized");
      this.title = "Expense Entry List";
      this.getExpenseItems();
   }
   getExpenseItems() {
      this.restService.getExpenseEntries()
      .subscribe( data => this.expenseEntries = data );
   }
}
最后,检查应用程序,您将看到以下响应。
请求失败

添加费用功能

让我们在 ExpenseEntryService 中添加一个新方法 addExpenseEntry() 以添加新的费用条目,如下所述-
addExpenseEntry(expenseEntry: ExpenseEntry): Observable<ExpenseEntry> {
   return this.httpClient.post<ExpenseEntry>(this.expenseRestUrl, expenseEntry, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}

更新费用输入功能

让我们在 ExpenseEntryService 中添加一个新方法 updateExpenseEntry() 来更新现有的费用条目,如下所述:
updateExpenseEntry(expenseEntry: ExpenseEntry): Observable<ExpenseEntry> {
   return this.httpClient.put<ExpenseEntry>(this.expenseRestUrl + "/" + expenseEntry.id, expenseEntry, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}

删除费用条目功能

让我们在 ExpenseEntryService 中添加一个新方法 deleteExpenseEntry() 以删除现有的费用条目,如下所述-
deleteExpenseEntry(expenseEntry: ExpenseEntry | number) : Observable<ExpenseEntry> {
   const id = typeof expenseEntry == 'number' ? expenseEntry : expenseEntry.id
   const url = `${this.expenseRestUrl}/${id}`;
   return this.httpClient.delete<ExpenseEntry>(url, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}

添加路由

使用下面的命令生成路由模块,如果之前没有完成。
ng generate module app-routing--module app--flat

输出

输出如下-
CREATE src/app/app-routing.module.ts (196 bytes) 
UPDATE src/app/app.module.ts (785 bytes)
这里,
CLI 生成 AppRoutingModule,然后在 AppModule
中进行配置
更新 AppRoutingModule (src/app/app.module.ts),如下所述-
import { NgModule } from '@angular/core'; 
import { Routes, RouterModule } from '@angular/router'; import { ExpenseEntryComponent } from './expense-entry/expense-entry.component'; 
import { ExpenseEntryListComponent } from './expense-entry-list/expense-entry-list.component'; 
const routes: Routes = [ 
   { path: 'expenses', component: ExpenseEntryListComponent }, 
   { path: 'expenses/detail/:id', component: ExpenseEntryComponent }, 
   { path: '', redirectTo: 'expenses', pathMatch: 'full' }]; 
@NgModule({ 
   imports: [RouterModule.forRoot(routes)], 
   exports: [RouterModule] }) 
export class AppRoutingModule { }
在这里,我们为我们的费用列表和费用详细信息组件添加了路由。
更新 AppComponent 模板 (src/app/app.component.html) 以包含 router-outletrouterLink。
<!--Navigation--> 
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top"> 
<div class="container"> 
   <a class="navbar-brand" href="#">{{ title }}</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"> 
      <span class="navbar-toggler-icon"></span> 
   </button> 
   <div class="collapse navbar-collapse" id="navbarResponsive"> 
      <ul class="navbar-nav ml-auto"> 
         <li class="nav-item active"> 
            <a class="nav-link" href="#">Home 
               <span class="sr-only" routerLink="/">(current)</span> 
            </a> 
         </li> 
         <li class="nav-item"> 
            <a class="nav-link" routerLink="/expenses">Report</a> 
         </li> 
         <li class="nav-item"> 
            <a class="nav-link" href="#">Add Expense</a> 
         </li> 
         <li class="nav-item"> 
            <a class="nav-link" href="#">About</a> 
         </li> 
      </ul> 
   </div> 
</div> 
</nav> 
<router-outlet></router-outlet>
打开 ExpenseEntryListComponent 模板 (src/app/expense-entry-list/expense-entry-list.component.html) 并包括每个费用条目的查看选项。
<table class="table table-striped"> 
   <thead> 
      <tr> 
         <th>Item</th>
         <th>Amount</th> 
         <th>Category</th> 
         <th>Location</th> 
         <th>Spent On</th> 
         <th>View</th> 
      </tr> 
   </thead> 
   <tbody> 
      <tr *ngFor="let entry of expenseEntries"> 
         <th scope="row">{{ entry.item }}</th> 
         <th>{{ entry.amount }}</th> 
         <td>{{ entry.category }}</td> 
         <td>{{ entry.location }}</td> 
         <td>{{ entry.spendOn | date: 'medium' }}</td> 
         <td><a routerLink="../expenses/detail/{{ entry.id }}">View</a></td> 
      </tr> 
   </tbody> 
</table>
在这里,我们更新了费用清单并添加了一个新列来显示查看选项。
打开 ExpenseEntryComponent (src/app/expense-entry/expense-entry.component.ts) 并添加功能以获取当前选定的费用条目。可以先通过 paramMap 获取 id,然后使用 ExpenseEntryService 中的 getExpenseEntry() 方法。
this.expenseEntry$ = this.route.paramMap.pipe(  
   switchMap(params => { 
      this.selectedId = Number(params.get('id')); 
      return 
this.restService.getExpenseEntry(this.selectedId); })); 
   this.expenseEntry$.subscribe( (data) => this.expenseEntry = data );
更新 ExpenseEntryComponent 并添加进入费用列表的选项。
goToList() { 
   this.router.navigate(['/expenses']); 
}
ExpenseEntryComponent 的完整代码如下-
import { Component, OnInit } from '@angular/core'; import { ExpenseEntry } from '../expense-entry'; import { ExpenseEntryService } from '../expense-entry.service'; 
import { Router, ActivatedRoute } from '@angular/router'; 
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators'; 
@Component({ 
   selector: 'app-expense-entry', 
   templateUrl: './expense-entry.component.html', 
   styleUrls: ['./expense-entry.component.css'] 
}) 
export class ExpenseEntryComponent implements OnInit { 
   title: string; 
   expenseEntry$ : Observable<ExpenseEntry>; 
   expenseEntry: ExpenseEntry = {} as ExpenseEntry; 
   selectedId: number; 
   constructor(private restService : ExpenseEntryService, private router : Router, private route : 
ActivatedRoute ) { } 
   ngOnInit() { 
      this.title = "Expense Entry"; 
   this.expenseEntry$ = this.route.paramMap.pipe( 
      switchMap(params => { 
         this.selectedId = Number(params.get('id')); 
         return 
this.restService.getExpenseEntry(this.selectedId); })); 
   this.expenseEntry$.subscribe( (data) => this.expenseEntry = data ); 
   } 
   goToList() { 
      this.router.navigate(['/expenses']); 
   } 
}
打开 ExpenseEntryComponent (src/app/expense-entry/expense-entry.component.html) 模板并添加一个新按钮以导航回费用列表页面。
<div class="col-sm" style="text-align: right;"> 
   <button type="button" class="btn btn-primary" (click)="goToList()">Go to List</button>  
   <button type="button" class="btn btn-primary">Edit</button> 
</div>
这里,我们在 Edit 按钮之前添加了 Go to List 按钮。
使用以下命令运行应用程序-
ng serve
应用程序的最终输出如下-
嵌套路由
单击第一个条目的查看选项将导航到详细信息页面并显示所选费用条目,如下所示-
嵌套路由

启用登录和注销功能

创建一个新服务 AuthService 以对用户进行身份验证。
ng generate service auth
CREATE src/app/auth.service.spec.ts (323 bytes)
CREATE src/app/auth.service.ts (133 bytes)
打开 AuthService 并包含以下代码。
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { tap, delay } from 'rxjs/operators';
@Injectable({
   providedIn: 'root'
})
export class AuthService {
   isUserLoggedIn: boolean = false;
   login(userName: string, password: string): Observable
    
   
    
      {
      console.
    log(userName);
      console.
    log(password);
      
    this.isUserLoggedIn = userName == 
    'admin' && password == 'admin';
      localStorage.
    setItem(
    'isUserLoggedIn', 
    this.isUserLoggedIn ? 
    "true" : 
    "false"); 
   
    return of(
    this.
    isUserLoggedIn).pipe(
      delay(1000),
      tap(val => { 
         console.
    log(
    "Is User Authentication is successful: " + val); 
      })
   );
   }
   logout():
     void {
   
    this.isUserLoggedIn =
     false;
      localStorage.
    removeItem('isUserLoggedIn'); 
   }
   constructor() { }
}
    
   
这里,
我们编写了两个方法,loginlogout login 方法的目的是验证用户,如果用户验证成功,它会将信息存储在 localStorage 中,然后返回 true。 身份验证是用户名和密码应该是admin。 我们没有使用任何后端。相反,我们使用 Observables 模拟了 1 秒的延迟。 logout 方法的目的是使用户无效并删除存储在 localStorage 中的信息。
使用以下命令创建一个 login 组件-
ng generate component login
CREATE src/app/login/login.component.html (20 bytes)
CREATE src/app/login/login.component.spec.ts (621 bytes)
CREATE src/app/login/login.component.ts (265 bytes)
CREATE src/app/login/login.component.css (0 bytes)
UPDATE src/app/app.module.ts (1207 bytes)
打开 LoginComponent 并包含以下代码-
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { AuthService } from '../auth.service';
import { Router } from '@angular/router';
@Component({
   selector: 'app-login',
   templateUrl: './login.component.html',
   styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
   userName: string;
   password: string;
   formData: FormGroup;
   constructor(private authService : AuthService, private router : Router) { }
   ngOnInit() {
      this.formData = new FormGroup({
         userName: new FormControl("admin"),
         password: new FormControl("admin"),
      });
   }
   onClickSubmit(data: any) {
      this.userName = data.userName;
      this.password = data.password;
      console.log("Login page: " + this.userName);
      console.log("Login page: " + this.password);
      this.authService.login(this.userName, this.password)
         .subscribe( data => { 
            console.log("Is Login Success: " + data); 
      
           if(data) this.router.navigate(['/expenses']); 
      });
   }
}
这里,
使用反应式形式。 导入 AuthService 和 Router 并在构造函数中进行配置。 创建了一个 FormGroup 实例并包含两个 FormControl 实例,一个用于用户名,另一个用于密码。 创建了一个 onClickSubmit 以使用 authService 验证用户,如果成功,则导航到费用列表。
打开 LoginComponent 模板并包含以下模板代码。
<!--Page Content-->
<div class="container">
   <div class="row">
      <div class="col-lg-12 text-center" style="padding-top: 20px;">
         <div class="container box" style="margin-top: 10px; padding-left: 0px; padding-right: 0px;">
            <div class="row">
               <div class="col-12" style="text-align: center;">
                                    <form [formGroup]="formData" (ngSubmit)="onClickSubmit(formData.value)" 
                                          class="form-signin">
                                    <h2 class="form-signin-heading">Please sign in</h2>
                                    <label for="inputEmail" class="sr-only">Email address</label>
                                    <input type="text" id="username" class="form-control" 
                                          formControlName="userName" placeholder="Username" required autofocus>
                                    <label for="inputPassword" class="sr-only">Password</label>
                                    <input type="password" id="inputPassword" class="form-control" 
                                          formControlName="password" placeholder="Password" required>
                                    <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
                                    </form>
               </div>
            </div>
         </div>
      </div>
   </div>
</div>
这里,
创建了一个响应式表单并设计了一个登录表单。
onClickSubmit 方法附加到表单提交操作。
打开 LoginComponent 样式并包含以下 CSS 代码。
.form-signin {
   max-width: 330px;
   padding: 15px;
   margin: 0 auto;
}
input {
   margin-bottom: 20px;
}
这里添加了一些样式来设计登录表单。
使用以下命令创建注销组件-
ng generate component logout
CREATE src/app/logout/logout.component.html (21 bytes)
CREATE src/app/logout/logout.component.spec.ts (628 bytes)
CREATE src/app/logout/logout.component.ts (269 bytes)
CREATE src/app/logout/logout.component.css (0 bytes)
UPDATE src/app/app.module.ts (1368 bytes)
打开 LogoutComponent 并包含以下代码。
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth.service';
import { Router } from '@angular/router';
@Component({
   selector: 'app-logout',
   templateUrl: './logout.component.html',
   styleUrls: ['./logout.component.css']
})
export class LogoutComponent implements OnInit {
   constructor(private authService : AuthService, private router: Router) { }
   ngOnInit() {
      this.authService.logout();
      this.router.navigate(['/']);
   }
}
这里,
使用了 AuthService 的注销方法。 一旦用户注销,页面将重定向到主页 (/)。
使用以下命令创建守卫-
ng generate guard expense
CREATE src/app/expense.guard.spec.ts (364 bytes)
CREATE src/app/expense.guard.ts (459 bytes)
打开 ExpenseGuard 并包含以下代码-
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
@Injectable({
   providedIn: 'root'
})
export class ExpenseGuard implements CanActivate {
   constructor(private authService: AuthService, private router: Router) {}
   canActivate(
   next: ActivatedRouteSnapshot,
   state: RouterStateSnapshot): boolean | UrlTree {
      let url: string = state.url;
          return this.checkLogin(url);
      }
      checkLogin(url: string): true | UrlTree {
         console.log("Url: " + url)
         let val: string = localStorage.getItem('isUserLoggedIn');
         if(val != null && val == "true"){
            if(url == "/login")
               this.router.parseUrl('/expenses');
            else 
               return true;
         } else {
            return this.router.parseUrl('/login');
         }
      }
}
这里,
checkLogin 将检查 localStorage 是否有用户信息,如果可用,则返回 true。 如果用户登录并进入登录页面,它会将用户重定向到费用页面 如果用户未登录,则用户将被重定向到登录页面。
打开 AppRoutingModule (src/app/app-routing.module.ts) 并更新以下代码-
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ExpenseEntryComponent } from './expense-entry/expense-entry.component';
import { ExpenseEntryListComponent } from './expense-entry-list/expense-entry-list.component';
import { LoginComponent } from './login/login.component';
import { LogoutComponent } from './logout/logout.component';
import { ExpenseGuard } from './expense.guard';
const routes: Routes = [
   { path: 'login', component: LoginComponent },
   { path: 'logout', component: LogoutComponent },
   { path: 'expenses', component: ExpenseEntryListComponent, canActivate: [ExpenseGuard]},
   { path: 'expenses/detail/:id', component: ExpenseEntryComponent, canActivate: [ExpenseGuard]},
   { path: '', redirectTo: 'expenses', pathMatch: 'full' }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
这里,
导入的 LoginComponent 和 LogoutComponent。 导入的 ExpenseGuard。 创建了两个新路由,login 和 logout 分别访问 LoginComponent 和 LogoutComponent。 为 ExpenseEntryComponent 和 ExpenseEntryListComponen 添加新选项 canActivate
打开 AppComponent 模板并添加两个登录和注销链接。
<div class="collapse navbar-collapse" id="navbarResponsive">
   <ul class="navbar-nav ml-auto">
      <li class="nav-item active">
         <a class="nav-link" href="#">Home
            <span class="sr-only" routerLink="/">(current)</span>
         </a>
      </li>
      <li class="nav-item">
         <a class="nav-link" routerLink="/expenses">Report</a>
      </li>
      <li class="nav-item">
         <a class="nav-link" href="#">Add Expense</a>
      </li>
      <li class="nav-item">
         <a class="nav-link" href="#">About</a>
      </li>
      <li class="nav-item">
                  <div *ngIf="isUserLoggedIn; else isLogOut">
                        <a class="nav-link" routerLink="/logout">Logout</a>
                  </div>
                  <ng-template #isLogOut>
                              <a class="nav-link" routerLink="/login">Login</a>
                  </ng-template>
      </li>
   </ul>
</div>
打开 AppComponent 并更新以下代码-
import { Component } from '@angular/core';
import { AuthService } from './auth.service';
@Component({
   selector: 'app-root',
   templateUrl: './app.component.html',
   styleUrls: ['./app.component.css']
})
export class AppComponent {
   title = 'Expense Manager';
   isUserLoggedIn = false;
   constructor(private authService: AuthService) {}
   ngOnInit() {
      let storeData = localStorage.getItem("isUserLoggedIn");
      console.log("StoreData: " + storeData);
      if( storeData != null && storeData == "true")
         this.isUserLoggedIn = true;
      else
         this.isUserLoggedIn = false;
   }
}
在这里,我们添加了识别用户状态的逻辑,以便我们可以显示登录/注销功能。
打开 AppModule (src/app/app.module.ts) 并配置 ReactiveFormsModule
import { ReactiveFormsModule } from '@angular/forms'; 
imports: [ 
   ReactiveFormsModule 
]
现在,运行应用程序,应用程序打开登录页面。
ReactiveFormsModule
输入 admin 和 admin 作为用户名和密码,然后点击提交。应用程序处理登录并将用户重定向到费用列表页面,如下所示-
FormsModule
最后,您可以单击注销并退出应用程序。

添加/编辑/删除费用

添加新组件, EditEntryComponent 使用以下命令添加新费用条目并编辑现有费用条目
ng generate component EditEntry
CREATE src/app/edit-entry/edit-entry.component.html (25 bytes)
CREATE src/app/edit-entry/edit-entry.component.spec.ts (650 bytes)
CREATE src/app/edit-entry/edit-entry.component.ts (284 bytes)
CREATE src/app/edit-entry/edit-entry.component.css (0 bytes)
UPDATE src/app/app.module.ts (1146 bytes)
使用以下代码更新 EditEntryComponent-
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { ExpenseEntry } from '../expense-entry';
import { ExpenseEntryService } from '../expense-entry.service';
import { Router, ActivatedRoute } from '@angular/router';
@Component({
   selector: 'app-edit-entry',
   templateUrl: './edit-entry.component.html',
   styleUrls: ['./edit-entry.component.css']
})
export class EditEntryComponent implements OnInit {
   id: number;
   item: string;
   amount: number;
   category: string;
   location: string;
   spendOn: Date;
   formData: FormGroup;
   selectedId: number;
   expenseEntry: ExpenseEntry;
   constructor(private expenseEntryService : ExpenseEntryService, private router: Router, private route: ActivatedRoute) { }
   ngOnInit() {
      this.formData = new FormGroup({
         id: new FormControl(),
         item: new FormControl('', [Validators.required]),
         amount: new FormControl('', [Validators.required]),
         category: new FormControl(),
         location: new FormControl(),
         spendOn: new FormControl()
      });
      this.selectedId = Number(this.route.snapshot.paramMap.get('id'));
      if(this.selectedId != null && this.selectedId != 0) {
         this.expenseEntryService.getExpenseEntry(this.selectedId)
            .subscribe( (data) => 
               {
                  this.expenseEntry = data;
                  this.formData.controls['id'].setValue(this.expenseEntry.id);
                  this.formData.controls['item'].setValue(this.expenseEntry.item);
                  this.formData.controls['amount'].setValue(this.expenseEntry.amount);
                  this.formData.controls['category'].setValue(this.expenseEntry.category);
                  this.formData.controls['location'].setValue(this.expenseEntry.location);
                  this.formData.controls['spendOn'].setValue(this.expenseEntry.spendOn);
               })
      }
   }
   get itemValue() {
   return this.formData.get('item');
   }
   get amountValue() {
   return this.formData.get('amount');
   }
    onClickSubmit(data: any) {
   console.log('onClickSubmit fired');
   this.id = data.id;
   this.item = data.item;
   this.amount = data.amount;
   this.category = data.category;
   this.location = data.location;
   this.spendOn = data.spendOn;
   let expenseEntry : ExpenseEntry = {
      id: this.id,
       item: this.item,
       amount: this.amount,
       category: this.category,
       location: this.location,
       spendOn: this.spendOn,
       createdOn: new Date(2020, 5, 20)
   }
   console.log(expenseEntry);
      if(expenseEntry.id == null || expenseEntry.id == 0) {
         console.log('add fn fired');
      this.expenseEntryService.addExpenseEntry(expenseEntry)
         .subscribe( data => { console.log(data); this.router.navigate(['/expenses']); });
   } else {
         console.log('edit fn fired');
      this.expenseEntryService.updateExpenseEntry(expenseEntry)
         .subscribe( data => { console.log(data); this.router.navigate(['/expenses']); });
   }
    }
}
这里,
使用具有适当验证规则的 FormControlFormGroup 类在 ngOnInit 方法中创建了一个表单 formData ngOnInit 方法中加载了要编辑的费用条目。 创建了两个方法,itemValueamountValue 以获取用户分别输入的用于验证目的的 item 和 amount 值。 创建方法,onClickSubmit 以保存(添加/更新)费用条目。 使用费用服务来添加和更新费用条目。
使用费用表单更新 EditEntryComponent 模板,如下所示-
<!--Page Content-->
<div class="container">
   <div class="row">
   <div class="col-lg-12 text-center" style="padding-top: 20px;">
       <div class="container" style="padding-left: 0px; padding-right: 0px;">
       </div>
       <div class="container box" style="margin-top: 10px;">
<form [formGroup]="formData" (ngSubmit)="onClickSubmit(formData.value)" class="form" novalidate> 
  <div class="form-group">
    <label for="item">Item</label>
    <input type="hidden" class="form-control" id="id" formControlName="id">
    <input type="text" class="form-control" id="item" formControlName="item">
    <div
   *ngIf="!itemValue?.valid && (itemValue?.dirty ||itemValue?.touched)">
   <div [hidden]="!itemValue.errors.required">
      Item is required
   </div>
   </div>
  </div>
  <div class="form-group">
    <label for="amount">Amount</label>
    <input type="text" class="form-control" id="amount" formControlName="amount">
    <div
   *ngIf="!amountValue?.valid && (amountValue?.dirty ||amountValue?.touched)">
   <div [hidden]="!amountValue.errors.required">
      Amount is required
   </div>
   </div>
  </div>
  <div class="form-group">
    <label for="category">Category</label>
    <select class="form-control" id="category" formControlName="category">
      <option>Food</option>
      <option>Vegetables</option>
      <option>Fruit</option>
      <option>Electronic Item</option>
      <option>Bill</option>
    </select>
  </div>
  <div class="form-group">
    <label for="location">location</label>
    <input type="text" class="form-control" id="location" formControlName="location">
  </div>
  <div class="form-group">
    <label for="spendOn">spendOn</label>
    <input type="text" class="form-control" id="spendOn" formControlName="spendOn">
  </div>
<button class="btn btn-lg btn-primary btn-block" type="submit" [disabled]="!formData.valid">Submit</button>
</form>
       </div>
   </div>
    </div>
</div>
这里,
创建一个表单并将其绑定到该表单,formData在类中创建。 验证 itemamount 作为必需值。 在验证成功后调用 onClickSubmit 函数。
打开 EditEntryComponent 样式表并更新以下代码-
.form {
   max-width: 330px;
   padding: 15px;
   margin: 0 auto;
}
.form label {
   text-align: left;
   width: 100%;
}
input {
   margin-bottom: 20px;
}
在这里,我们设计了费用条目表单。
使用下面的命令添加 AboutComponent
ng generate component About
CREATE src/app/about/about.component.html (20 bytes)
CREATE src/app/about/about.component.spec.ts (621 bytes)
CREATE src/app/about/about.component.ts (265 bytes)
CREATE src/app/about/about.component.css (0 bytes)
UPDATE src/app/app.module.ts (1120 bytes)
打开 AboutComponent 并添加如下指定的标题-
import { Component, OnInit } from '@angular/core';
@Component({
   selector: 'app-about',
   templateUrl: './about.component.html',
   styleUrls: ['./about.component.css']
})
export class AboutComponent implements OnInit {
   title = "About";
   constructor() { }
   ngOnInit() {
   }
}
打开 AboutComponent 模板并更新如下指定的内容-
<!--Page Content-->
<div class="container">
   <div class="row">
   <div class="col-lg-12 text-center" style="padding-top: 20px;">
       <div class="container" style="padding-left: 0px; padding-right: 0px;">
      <div class="row">
          <div class="col-sm" style="text-align: left;">
         <h1>{{ title }}</h1>
          </div>
      </div>
       </div>
       <div class="container box" style="margin-top: 10px;">
      <div class="row">
          <div class="col" style="text-align: left;">
         <p>Expense management Application</p>
          </div>
      </div>
       </div>
   </div>
    </div>
</div>
为添加和编辑费用条目添加路由,如下所示
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ExpenseEntryComponent } from './expense-entry/expense-entry.component';
import { ExpenseEntryListComponent } from './expense-entry-list/expense-entry-list.component';
import { LoginComponent } from './login/login.component';
import { LogoutComponent } from './logout/logout.component';
import { EditEntryComponent } from './edit-entry/edit-entry.component';
import { AboutComponent } from './about/about.component';
import { ExpenseGuard } from './expense.guard';
const routes: Routes = [
   { path: 'about', component: AboutComponent },
   { path: 'login', component: LoginComponent },
   { path: 'logout', component: LogoutComponent },
   { path: 'expenses', component: ExpenseEntryListComponent, canActivate: [ExpenseGuard]},
   { path: 'expenses/detail/:id', component: ExpenseEntryComponent, canActivate: [ExpenseGuard]},
   { path: 'expenses/add', component: EditEntryComponent, canActivate: [ExpenseGuard]},
   { path: 'expenses/edit/:id', component: EditEntryComponent, canActivate: [ExpenseGuard]},
   { path: '', redirectTo: 'expenses', pathMatch: 'full' }
];
@NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
})
export class AppRoutingModule { }
在这里,我们添加了 关于、添加费用编辑费用路线。
ExpenseEntryListComponent 模板中添加 编辑删除 链接。
<table class="table table-striped">
   <thead>
         <tr>
         <th>Item</th>
         <th>Amount</th>
         <th>Category</th>
         <th>Location</th>
         <th>Spent On</th>
         <th>View</th>
               <th>Edit</th>
               <th>Delete</th>
         </tr>
   </thead>
   <tbody>
      <tr *ngFor="let entry of expenseEntries">
      <th scope="row">{{ entry.item }}</th>
      <th>{{ entry.amount }}</th>
      <td>{{ entry.category }}</td>
      <td>{{ entry.location }}</td>
      <td>{{ entry.spendOn | date: 'medium' }}</td>
      <td><a routerLink="../expenses/detail/{{ entry.id }}">View</a></td>
      <td><a routerLink="../expenses/edit/{{ entry.id }}">Edit</a></td>
      <td><a href="#" (click)="deleteExpenseEntry($event, entry.id)">Delete</a></td>
      </tr>
   </tbody>
</table>
在这里,我们又包含了两列。一列用于显示编辑链接,另一列用于显示删除链接。
更新 ExpenseEntryListComponent中的 deleteExpenseEntry方法,如下图
deleteExpenseEntry(evt, id) {
   evt.preventDefault();
   if(confirm("Are you sure to delete the entry?")) {
      this.restService.deleteExpenseEntry(id)
         .subscribe( data => console.log(data) );
      this.getExpenseItems();
   }
}
这里,我们要求确认删除并得到用户确认,调用费用服务中的 deleteExpenseEntry 方法删除所选费用项目。
将顶部 ExpenseEntryListComponent 模板中的 Edit 链接更改为 Add 链接,如下所示-
<div class="col-sm" style="text-align: right;">
   <button class="btn btn-primary" routerLink="/expenses/add">ADD</button> 
   <!--<button type="button" class="btn btn-primary">Edit</button>-->
</div>
ExpenseEntryComponent 模板中添加 编辑链接。
<div class="col-sm" style="text-align: right;">
   <button type="button" class="btn btn-primary" (click)="goToList()">Go to List</button>
    <button type="button" class="btn btn-primary" (click)="goToEdit()">Edit</button>
</div>
打开 ExpenseEntryComponent 并添加 goToEdit() 方法,如下所示-
goToEdit() {      
   this.router.navigate(['/expenses/edit', this.selectedId]); 
}
更新 AppComponent模板中的导航链接。
<!--Navigation-->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
   <div class="container">
      <a class="navbar-brand" href="#">{{ title }}</a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
         <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarResponsive">
         <ul class="navbar-nav ml-auto">
            <li class="nav-item active">
               <a class="nav-link" href="#">Home
                  <span class="sr-only" routerLink="/">(current)</span>
               </a>
            </li>
            <li class="nav-item">
               <a class="nav-link" routerLink="/expenses/add">Add Expense</a>
            </li>
            <li class="nav-item">
               <a class="nav-link" routerLink="/about">About</a>
            </li>
            <li class="nav-item">
                        <div *ngIf="isUserLoggedIn; else isLogOut">
                              <a class="nav-link" routerLink="/logout">Logout</a>
                        </div>
                        <ng-template #isLogOut>
                                    <a class="nav-link" routerLink="/login">Login</a>
                        </ng-template>
            </li>
         </ul>
      </div>
   </div>
</nav>
<router-outlet></router-outlet>
在这里,我们更新了 添加费用链接和 关于链接。
运行应用程序,输出将类似于如下所示-
expense
尝试使用费用列表页面中的 添加链接添加新费用。输出将类似于如下所示
添加
如下所示填写表格-
提交
如果数据填写不正确,验证码会提示如下-
alert
点击 提交。它将触发提交事件,数据将保存到后端并重定向到列表页面,如下所示-
backend
尝试使用费用列表页面中的"编辑"链接编辑现有费用。输出将类似于如下所示-
existing
点击 提交。会触发提交事件,数据将保存到后端并重定向到列表页面。
要删除项目,请点击删除链接。它将确认删除,如下所示-
trigger
最后,我们已经实现了在我们的应用程序中管理费用所需的所有功能。
昵称: 邮箱:
Copyright © 2022 立地货 All Rights Reserved.
备案号:京ICP备14037608号-4