本章基于以一个基本 Angular 应用快速上手的第二步 —— 添加导航。 在此开发阶段,本商店应用具有一个包含两个视图的商品名录:商品列表和商品详情。用户点击清单中的某个商品名称,就会在新视图中看到具有专门的 URL 或路由的详情页。
本页将指导你分三个步骤创建购物车:
HttpClient 从 .json 文件中检索配送数据来取得购物车中这些商品的运费。在 Angular 中, 服务是类的一个实例, 借助 Angular 的依赖注入体系,你可以在应用中的任意部分使用它。
现在, 用户可以浏览产品信息,而应用可以模拟分享产品,以及发出产品变更通知。
下一步是为用户提供一种把产品添加到购物车中的方法。 本章节将带领你添加一个 Buy 按钮并且建立一个购物车服务以保存购物车中的产品信息。
本节将引导你创建用于跟踪添加到购物车的产品的 CartService 。
cart 服务:ng generate service cart将
Product 接口从 ./products.ts 导入到 cart.service.ts 文件中,在 CartService 类中,定义一个 items 属性来存储购物车中当前产品的数组。 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;
}
/* . . . */
} addToCart() 方法会将产品附加到 items 数组中。getItems() 方法会收集用户加到购物车中的商品,并返回每个商品及其数量。clearCart() 方法返回一个空数组。本节会教你使用 CartService 来把一个商品添加到购物车中。
product-details.component.ts 中导入购物车服务。import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Product, products } from '../products';
import { CartService } from '../cart.service'; constructor() 中来注入它。export class ProductDetailsComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private cartService: CartService
) { }
} 定义 addToCart() 方法,该方法会当前商品添加到购物车中。 export class ProductDetailsComponent implements OnInit {
addToCart(product: Product) {
this.cartService.addToCart(product);
window.alert('Your product has been added to the cart!');
}
} addToCart() 方法做了如下事情:
CartService addToCart() 方法去添加产品到购物车中。product-details.component.html 中,添加一个带有 Buy 标签的按钮,并且把其 click() 事件绑定到 addToCart() 方法上。 这段代码会为产品详情模板添加一个 Buy 按钮,并把当前产品添加到购物车中。 <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>
Buy 按钮如预期般出现了,并且单击某个产品的名称,以展示其详情。 

为了让顾客看到他们的购物车,你可以用两步创建购物车视图:
要创建购物车视图,可遵循与创建 ProductDetailsComponent 相同的步骤,并且为这个新组件配置路由。
cart 的新组件: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() 。
CartComponent 已添加到 app.module.ts 中模块的 declarations 中。 import { CartComponent } from './cart/cart.component';
@NgModule({
declarations: [
AppComponent,
TopBarComponent,
ProductListComponent,
ProductAlertsComponent,
ProductDetailsComponent,
CartComponent,
], 打开 app.module.ts,为组件 CartComponent 添加一个路由,其路由为 cart : @NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
RouterModule.forRoot([
{ path: '', component: ProductListComponent },
{ path: 'products/:productId', component: ProductDetailsComponent },
{ path: 'cart', component: CartComponent },
])
], 修改 "Checkout" 按钮,以便让它路由到 /cart。 在 top-bar.component.html 中添加一个指向 /cart 的 routerLink 指令。 <a routerLink="/cart" class="button fancy-button">要查看新的购物车组件,请点击“Checkout”按钮。你会看到默认文本“cart works!”,该 URL 的格式为
<i class="material-icons">shopping_cart</i>Checkout
</a>
https://getting-started.stackblitz.io/cart,其中的 getting-started.stackblitz.io 部分可能与你的 StackBlitz 项目不同。 
本节将告诉你如何修改购物车组件以使用购物车服务来显示购物车中的商品。
cart.component.ts 中,从 cart.service.ts 文件中导入 CartService。import { Component } from '@angular/core';
import { CartService } from '../cart.service'; CartService,以便购物车组件可以使用它。export class CartComponent {
constructor(
private cartService: CartService
) { }
} 定义 items 属性,以便把商品存放在购物车中。 export class CartComponent {
items = this.cartService.getItems();
constructor(
private cartService: CartService
) { }
} 这段代码使用 CartService 的 getItems() 方法来设置条目。
*ngFor 的 <div> 来显示每个购物车商品的名字和价格。 生成的 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
}
]
要使用 Angular 的 HTTP 客户端之前,你必须先配置你的应用来使用 HttpClientModule。
Angular 的 HttpClientModule 中注册了在整个应用中使用 HttpClient 服务的单个实例所需的服务提供者。
app.module.ts 的顶部从 @angular/common/http 包中导入 HttpClientModule 以及其它导入项。 由于有很多其它导入项,因此这里的代码片段省略它们,以保持简洁。请确保现有的导入都还在原地。import { HttpClientModule } from '@angular/common/http'; 把 HttpClientModule 添加到 AppModule @NgModule() 的 imports 数组中,以便全局注册 Angular 的 HttpClient。 @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 { } 下一步是注入 HttpClient 服务到你的服务中, 这样你的应用可以获取数据并且与外部API和资源互动。
@angular/common/http 包中导入 HttpClient。import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Product } from './products'; 把 HttpClient 注入到 CartService 的构造函数中: export class CartService {
items: Product[] = [];
constructor(
private http: HttpClient
) {}
/* . . . */
} 要从 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 的组件: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() { }
} 在 app.module.ts 中,添加一个配送路由。其 path 为 shipping,其 component 为 ShippingComponent。 @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 项目不同。
这个章节将指导你修改 ShappingComponent 以通过HTTP从 shipping.json 文件中提取商品数据。
shipping.component.ts 中导入 CartService。import { Component } from '@angular/core';
import { CartService } from '../cart.service'; 把购物车服务注入到 ShippingComponent 的 constructor() 构造函数中: constructor(private cartService: CartService) { } shippingCosts 属性,并从 CartService 中利用购物车服务的 getShippingPrices() 方法设置它。export class ShippingComponent {
shippingCosts = this.cartService.getShippingPrices();
} 利用 async 管道修改配送组件的模板,以显示配送类型和价格: <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>点击 Checkout 按钮,查看更新后的购物车。注意,修改本应用会导致预览窗格刷新,这会清空购物车。
<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>

点击此链接可以导航到运费页。
