Angular 管理数据
管理数据
本章基于以一个基本 Angular 应用快速上手的第二步 —— 添加导航。 在此开发阶段,本商店应用具有一个包含两个视图的商品名录:商品列表和商品详情。用户点击清单中的某个商品名称,就会在新视图中看到具有专门的 URL 或路由的详情页。
本页将指导你分三个步骤创建购物车:
- 修改商品详情视图,让它包含一个 “Buy” 按钮,它会把当前商品添加到由 "购物车服务" 管理的商品列表中。
- 添加一个购物车组件,它会显示购物车中的商品。
- 添加一个配送组件,它会使用 Angular 的
HttpClient
从 .json
文件中检索配送数据来取得购物车中这些商品的运费。
创建购物车服务
在 Angular 中, 服务是类的一个实例, 借助 Angular 的依赖注入体系,你可以在应用中的任意部分使用它。
现在, 用户可以浏览产品信息,而应用可以模拟分享产品,以及发出产品变更通知。
下一步是为用户提供一种把产品添加到购物车中的方法。 本章节将带领你添加一个 Buy 按钮并且建立一个购物车服务以保存购物车中的产品信息。
定义购物车服务
本节将引导你创建用于跟踪添加到购物车的产品的 CartService
。
- 在终端中通过运行以下命令生成一个新的
cart
服务: - 将
Product
接口从 ./products.ts
导入到 cart.service.ts
文件中,在 CartService
类中,定义一个 items
属性来存储购物车中当前产品的数组。 - 定义把商品添加到购物车、返回购物车商品以及清除购物车商品的方法:
-
addToCart()
方法会将产品附加到 items
数组中。 -
getItems()
方法会收集用户加到购物车中的商品,并返回每个商品及其数量。 -
clearCart()
方法返回一个空数组。
ng generate service cart
import { Product } from './products';
/* . . . */
export class CartService {
items: Product[] = [];
/* . . . */
}
export class CartService {
items: Product[] = [];
/* . . . */
addToCart(product: Product) {
this.items.push(product);
}
getItems() {
return this.items;
}
clearCart() {
this.items = [];
return this.items;
}
/* . . . */
}
使用购物车服务
本节会教你使用 CartService
来把一个商品添加到购物车中。
- 在
product-details.component.ts
中导入购物车服务。 - 通过把购物车服务注入到这里的
constructor()
中来注入它。 - 定义
addToCart()
方法,该方法会当前商品添加到购物车中。 - 以当前'product'作为参数。
- 使用
CartService
addToCart()
方法去添加产品到购物车中。 - 显示一条你已经添加了一个产品到购物车到消息。
- 在
product-details.component.html
中,添加一个带有 Buy 标签的按钮,并且把其 click()
事件绑定到 addToCart()
方法上。 这段代码会为产品详情模板添加一个 Buy 按钮,并把当前产品添加到购物车中。 - 刷新应用,以验证新的
Buy
按钮如预期般出现了,并且单击某个产品的名称,以展示其详情。 - 点击“Buy”按钮来把该商品添加到购物车中存储的商品列表中,并显示一条确认消息。
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Product, products } from '../products';
import { CartService } from '../cart.service';
export class ProductDetailsComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private cartService: CartService
) { }
}
export class ProductDetailsComponent implements OnInit {
addToCart(product: Product) {
this.cartService.addToCart(product);
window.alert('Your product has been added to the cart!');
}
}
addToCart()
方法做了如下事情:
<h2>Product Details</h2>
<div *ngIf="product">
<h3>{{ product.name }}</h3>
<h4>{{ product.price | currency }}</h4>
<p>{{ product.description }}</p>
<button (click)="addToCart(product)">Buy</button>
</div>
创建购物车视图
为了让顾客看到他们的购物车,你可以用两步创建购物车视图:
- 创建一个购物车组件并配置指向这个新组件的路由。
- 显示购物车商品
设置该组件
要创建购物车视图,可遵循与创建 ProductDetailsComponent
相同的步骤,并且为这个新组件配置路由。
- 通过运行以下命令在终端中生成一个名为
cart
的新组件: - 请注意,新创建的
CartComponent
已添加到 app.module.ts
中模块的 declarations
中。 - 打开
app.module.ts
,为组件 CartComponent
添加一个路由,其路由为 cart
: - 修改 "Checkout" 按钮,以便让它路由到
/cart
。 在 top-bar.component.html
中添加一个指向 /cart
的 routerLink
指令。 - 要查看新的购物车组件,请点击“Checkout”按钮。你会看到默认文本“cart works!”,该 URL 的格式为
https://getting-started.stackblitz.io/cart
,其中的 getting-started.stackblitz.io 部分可能与你的 StackBlitz 项目不同。
ng generate component cart
此命令将生成 cart.component.ts
文件及其关联的模板和样式文件。
import { Component } from '@angular/core';
@Component({
selector: 'app-cart',
templateUrl: './cart.component.html',
styleUrls: ['./cart.component.css']
})
export class CartComponent {
constructor() { }
}
StackBlitz 还在组件中默认生成一个 ngOnInit()
。对于本教程,你可以忽略 CartComponent
的 ngOnInit()
。
import { CartComponent } from './cart/cart.component';
@NgModule({
declarations: [
AppComponent,
TopBarComponent,
ProductListComponent,
ProductAlertsComponent,
ProductDetailsComponent,
CartComponent,
],
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
RouterModule.forRoot([
{ path: '', component: ProductListComponent },
{ path: 'products/:productId', component: ProductDetailsComponent },
{ path: 'cart', component: CartComponent },
])
],
<a routerLink="/cart" class="button fancy-button">
<i class="material-icons">shopping_cart</i>Checkout
</a>
显示购物车商品
本节将告诉你如何修改购物车组件以使用购物车服务来显示购物车中的商品。
- 在
cart.component.ts
中,从 cart.service.ts
文件中导入 CartService
。 - 注入
CartService
,以便购物车组件可以使用它。 - 定义
items
属性,以便把商品存放在购物车中。 - 修改模板,加上标题,用带有
*ngFor
的 <div>
来显示每个购物车商品的名字和价格。 - 验证你的购物车如预期般工作:
- 点击 My Store
- 单击商品名称以显示其详细信息。
- 点击Buy 将商品添加到购物车。
- 点击Checkout查看购物车。
import { Component } from '@angular/core';
import { CartService } from '../cart.service';
export class CartComponent {
constructor(
private cartService: CartService
) { }
}
export class CartComponent {
items = this.cartService.getItems();
constructor(
private cartService: CartService
) { }
}
这段代码使用 CartService
的 getItems()
方法来设置条目。
生成的 CartComponent
模板如下:
<h3>Cart</h3>
<div class="cart-item" *ngFor="let item of items">
<span>{{ item.name }}</span>
<span>{{ item.price | currency }}</span>
</div>
检索运费价格
服务器通常采用流的形式返回数据。 流是很有用的,因为它们可以很容易地转换返回的数据,也可以修改你请求数据的方式。 Angular 的 HTTP 客户端( HttpClient
)是一种内置的方式,可以从外部 API 中获取数据,并以流的形式提供给你的应用。
本节会为你展示如何使用 HttpClient
从外部文件中检索运费。
在本指南的 StackBlitz 应用中,通过 assets/shipping.json
文件提供了一些预定义的配送数据。你可以利用这些数据为购物车中的商品添加运费。
[
{
"type": "Overnight",
"price": 25.99
},
{
"type": "2-Day",
"price": 9.99
},
{
"type": "Postal",
"price": 2.99
}
]
配置 AppModule 以使用 HttpClient
要使用 Angular 的 HTTP 客户端之前,你必须先配置你的应用来使用 HttpClientModule
。
Angular 的 HttpClientModule
中注册了在整个应用中使用 HttpClient
服务的单个实例所需的服务提供者。
- 在
app.module.ts
的顶部从 @angular/common/http
包中导入 HttpClientModule
以及其它导入项。 由于有很多其它导入项,因此这里的代码片段省略它们,以保持简洁。请确保现有的导入都还在原地。 - 把
HttpClientModule
添加到 AppModule
@NgModule()
的 imports
数组中,以便全局注册 Angular 的 HttpClient
。
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
ReactiveFormsModule,
RouterModule.forRoot([
{ path: '', component: ProductListComponent },
{ path: 'products/:productId', component: ProductDetailsComponent },
{ path: 'cart', component: CartComponent },
])
],
declarations: [
AppComponent,
TopBarComponent,
ProductListComponent,
ProductAlertsComponent,
ProductDetailsComponent,
CartComponent,
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
配置 CartService 以使用 HttpClient
下一步是注入 HttpClient
服务到你的服务中, 这样你的应用可以获取数据并且与外部API和资源互动。
- 从
@angular/common/http
包中导入 HttpClient
。 - 把
HttpClient
注入到 CartService
的构造函数中:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Product } from './products';
export class CartService {
items: Product[] = [];
constructor(
private http: HttpClient
) {}
/* . . . */
}
配置 CartService 以得到商品价格
要从 shapping.json
中得到商品数据, 你可以使用 HttpClient
get()
方法。
- 在
cart.service.ts
中 clearCart()
方法下面,定义一个新的 getShippingPrices()
方法,该方法会调用 HttpClient#get()
方法。
export class CartService {
/* . . . */
getShippingPrices() {
return this.http.get<{type: string, price: number}[]>('/assets/shipping.json');
}
}
创建配送组件
现在你的应用已经可以检索配送数据了,你还要创建一个配送组件和相关的模板。
- 在终端窗口中运行如下命令,以生成名为
shipping
的组件: - 在
app.module.ts
中,添加一个配送路由。其 path
为 shipping
,其 component 为 ShippingComponent
。
ng generate component shipping
右键单击 app
文件夹,选择 Angular Generator 和 Component 来生成一个名为 shipping
的新组件。
import { Component } from '@angular/core';
@Component({
selector: 'app-shipping',
templateUrl: './shipping.component.html',
styleUrls: ['./shipping.component.css']
})
export class ShippingComponent {
constructor() { }
}
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
ReactiveFormsModule,
RouterModule.forRoot([
{ path: '', component: ProductListComponent },
{ path: 'products/:productId', component: ProductDetailsComponent },
{ path: 'cart', component: CartComponent },
{ path: 'shipping', component: ShippingComponent },
])
],
declarations: [
AppComponent,
TopBarComponent,
ProductListComponent,
ProductAlertsComponent,
ProductDetailsComponent,
CartComponent,
ShippingComponent
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
新的配送组件尚未链接到任何其它组件,但你可以通过输入其路由指定的 URL 在预览窗格中看到它的模板。该 URL 具有以下模式:https://angular-ynqttp--4200.local.webcontainer.io/shipping
,其中的 gets-started.stackblitz.io 部分可能与你的 StackBlitz 项目不同。
配置 ShippingComponent 以使用 CartService
这个章节将指导你修改 ShappingComponent
以通过HTTP从 shipping.json
文件中提取商品数据。
- 在
shipping.component.ts
中导入 CartService
。 - 把购物车服务注入到
ShippingComponent
的 constructor()
构造函数中: - 定义一个
shippingCosts
属性,并从 CartService
中利用购物车服务的 getShippingPrices()
方法设置它。 - 利用
async
管道修改配送组件的模板,以显示配送类型和价格: - 在购物车视图中添加一个到配送视图的链接:
- 点击 Checkout 按钮,查看更新后的购物车。注意,修改本应用会导致预览窗格刷新,这会清空购物车。
import { Component } from '@angular/core';
import { CartService } from '../cart.service';
constructor(private cartService: CartService) { }
export class ShippingComponent {
shippingCosts = this.cartService.getShippingPrices();
}
<h3>Shipping Prices</h3>
<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
<span>{{ shipping.type }}</span>
<span>{{ shipping.price | currency }}</span>
</div>
async
管道从数据流中返回最新值,并在所属组件的生命期内持续返回。当 Angular 销毁该组件时,async
管道会自动停止。
<h3>Cart</h3>
<p>
<a routerLink="/shipping">Shipping Prices</a>
</p>
<div class="cart-item" *ngFor="let item of items">
<span>{{ item.name }}</span>
<span>{{ item.price | currency }}</span>
</div>
点击此链接可以导航到运费页。