Beginner's Guide to ngrx and Angular
介绍
状态管理是一个在处理应用程序数据结构时始终会想到的术语。
大型软件系统开发和维护中的最大问题是复杂性-大型系统难以理解。
反应式编程是指我们对随着时间流逝而流向我们的数据作出反应。
在本文中,我们将介绍ngrx的基础知识及其在Angular中的应用。
源代码
可以在此GitHub存储库中找到本文中实现的源代码。
什么是ngrx?
ngrx是Angular的一组RxJS驱动的状态管理库,其灵感来自Redux,Redux是一种流行且可预测的JavaScript应用程序状态管理容器。 它是由Angular Developer Advocate在2013年开发的。
以下是ngrx给我们带来的一些好处:
ngrx旨在将反应性扩展引入Angular。
@ ngrx / store将所有应用程序状态的类似Redux的单个存储区带到Angular。
ngrx / store是Redux的实现,它是使用RxJS开发的,同时保留了Redux的核心概念和API。
受Redux启发的ngrx与它共享相同的原理,并使用RxJS对其进行增压。
我们将在以下各节中介绍@ ngrx / store的内部工作原理。
什么是RxJS?
RxJS是用于反应式编程的JavaScript库,允许您使用异步或基于回调的数据流。
谈论流-流是时间上的一系列值。 实时的事件和数据流(我们称为
使用RxJS,我们将编写如下内容:
1 2 3 4 5 6 7 | var button = document.querySelector('button'); Rx.Observable.fromEvent(button, 'click') .subscribe(() => console.log('Clicked!')); var arr = Rx.Observable.of(90, 80) .subscribe((v) => console.log('Value:', v)); |
您会发现,使用RxJS,我们只需很少的代码就可以实现很多目标。
什么是Redux?
如前所述,Redux是JavaScript应用程序的状态管理库。 尽管它最初是为React社区开发的,但也可以在原始JavaScript或任何其他JavaScript框架中使用。
Redux是一个实现Flux思想的库。 Flux是一种流行的单向数据流(单向流)的设计模式,最早由Facebook提出。
Redux中应用程序的状态保留在商店中。 使用传输到称为
核心概念
在深入探讨@ ngrx / store的工作方式/原因之前,让我们看一下核心概念。 使用@ ngrx / store开发的应用程序必须处理Store,Reducers,State和Action。
商店
简而言之,存储是我们应用程序的"数据库"。 它包含在我们的应用程序中定义的不同状态。 因此,状态是不可变的,只能通过动作来改变。
该商店将整个应用程序状态组合到一个实体中,充当Web应用程序的数据库。 就像传统的数据库一样,它代表了应用程序的记录点,您的商店可以视为客户端的"单一事实来源"。
减速器
如果商店是应用程序的数据库,那么约简器就是表。 约简器是一个纯函数,它接受两个参数-一个动作和一个具有与事件关联的类型和可选数据的先前状态。
1 2 3 4 5 6 7 8 9 10 | export function reducer(state = initialState, action: articles.Actions):State { switch(action.type) { case 'ADD_ARTICLE': return { articles: [...state.articles,action.payload] } default: return state; } } |
州
状态是单个不变的数据结构。 状态是组成商店的原因。 如前所述,化简器就像表一样,因此state是表中的字段。
动作
Store包含了应用程序的状态,Reducer获得了存储的状态片或状态部分,但是当需要时我们如何更新Store? 这就是行动的作用。 动作表示从应用程序分发到商店的信息有效负载,通常由用户交互触发。
1 2 3 4 5 6 7 8 9 10 11 | // Action interface export interface Action { type: string, payload?: any } // Action with payload dispatch({type: 'ADD_ARTICLE', payload: {link: 'github.com/philipszdavido', points:90}}) // Action without payload dispatch({type:'LOAD_LINKS'}) |
调度动作后,Reducer会根据动作类型采用它并应用有效负载,并输出新状态。
总结一下:存储包括整个状态,reduce返回状态的片段,而动作是预定义的用户触发的事件,用于传达给定状态框架应如何更改。
店铺优势
我们已经看到@ ngrx / store在管理应用程序中的状态方面多么有效和有用。 但是在继续展示它在Angular中的应用之前,我们将先看一下它的优势。
该商店的主要优势是集中状态,测试,性能和DevTools。
集中状态:商店中的状态保存在一个目录中。 它使预测商店的更新或更改以及跟踪问题变得更加容易
测试:为纯函数编写测试很容易。 由于商店由减速器组成-它们是纯函数,仅使用其输入来产生其输出而没有副作用。 只需输入,然后声明输出即可。
性能:反应性导致的单向数据流状态变化使其变得非常快速和高效。
DevTools:已经创建了一些很棒的工具来帮助开发人员。 一个示例是ngrx / store-devtool,它可以帮助开发人员在开发过程中进行"时间旅行"。 它还具有一些不错的功能,可帮助提供操作和状态更改的历史记录。
ngrx / store:幕后花絮
@ ngrx / store是用
要很好地理解一个概念,您必须查看源代码。 @ ngrx / store对我来说一直是一个谜,直到我从其Git存储库下载该项目并深入到源代码中时,我才得到图片。 我看到了图书馆是如何建造的。 这样,我真的很熟悉这些东西。
查看代码,您将看到@ ngrx / store有四个核心类
ngrx在实践中
现在,我们来展示一下如何在Angular中使用@ ngrx / store。 为了演示@ ngrx / store的功能,我们将构建一个简单的"在线存储",该文件将允许用户执行以下操作:
查看产品清单
查看特定产品
用户可以将产品添加到他们的购物车
用户可以从购物车中删除产品
样品申请
我们将使用
1 | $ npm install angular/cli -g |
在这里,我们全局安装了
建立
现在设置好了。 我们将项目文件夹称为"在线存储"。 要搭建项目,请运行以下命令:
1 | $ ng new online-store --minimal |
请注意使用
现在,我们的目录结构将如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ├── online-store ├── src ├── app ├── app.component.ts └── app.module.ts ├── assets └── .gitkeep ├── environment ├── environment.prod.ts └── environment.ts ├── index.html ├── main.ts ├── polyfills.ts ├── style.css ├── tsconfig.app.json └── typings.d.ts ├── .angular-cli.json ├── .gitignore ├── package.json └── tsconfig.json |
现在,我们将安装Bootstrap以使我们的应用程序具有响应性和美观的外观:
1 | $ npm install bootstrap -S |
将" style.css"重新显示为" style.scss",然后打开" style.scss"并添加以下行:
1 | @import"~bootstrap/scss/bootstrap.scss" |
接下来,我们引入@ ngrx / store库:
1 | $ npm install @ngrx/store @ngrx/core -S |
我们的应用程序将包含三个组件:
要搭建上述组件,请运行以下命令:
1 2 3 | $ ng g c products --inline-style=true --spec=false $ ng g c cart --inline-style=true --spec=false $ ng g c product --inline-style=true --spec=false |
注意我们传递给
在这里,传递
接下来,我们将路由添加到我们的应用程序。 我们将创建三个路线:
/ products:这将是我们的索引路径。 它将激活
/购物车:这将激活
/ product /:id:此路由具有
要在我们的应用中启用路由,我们必须从
1 2 3 4 5 6 7 | // app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; ... import { Routes, RouterModule } from '@angular/router' ... |
接下来,我们定义类型为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; ... import { Routes, RouterModule } from '@angular/router' ... const routes: Routes = [ { path: '', redirectTo: '/products', pathMatch: 'full' }, { path: 'products', component: ProductsComponent }, { path: 'cart', component: CartComponent }, { path: 'product/:id', component: ProductComponent }, { path: '**', redirectTo: '', pathMatch: 'full' } ]; ... |
这代表了我们的应用可以进入的所有可能的路由器状态。
如前所述,我们的应用程序具有三种途径:"产品","产品/:id"和"购物车"。 我们在此处添加了一些其他配置:
现在,要激活应用程序中的路由系统,我们在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | // app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { ProductsComponent } from './products/products.component'; import { CartComponent } from './cart/cart.component'; import { ProductComponent } from './product/product.component'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = [ { path: '', redirectTo: '/products', pathMatch: 'full' }, { path: 'products', component: ProductsComponent }, { path: 'cart', component: CartComponent }, { path: 'product/:id', component: ProductComponent }, { path: '**', redirectTo: '', pathMatch: 'full' } ]; @NgModule({ declarations: [ AppComponent, ProductsComponent, CartComponent, ProductComponent ], imports: [ BrowserModule, RouterModule.forRoot(routes) ], providers: [], bootstrap: [AppComponent] }); export class AppModule { } |
最后,我们需要告诉Angular Router在哪里可以将我们的应用程序路由配置放置在DOM中。
我们将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
定义我们的减速器
现在,我们将定义我们的商店,并开始在其上添加减速器。 让我们为所有与商店相关的文件创建一个名为" store"的中央文件夹:
1 | $ mkdir src/app/store |
OK,现在让我们创建化简文件" reducer.ts":
1 | $ touch src/app/store/reducer.ts |
该文件将包含我们的reducer函数,稍后我们将进行介绍。 如前所述,此应用程序的某些功能是处理"从购物车中移除产品"和"将产品添加到购物车"。 因此,我们的减速机将处理移除和添加到购物车中的产品。
回到我们之前所说的," reducer.ts"文件将包含一个reducer函数,该函数接受以前的状态和当前调度的动作作为参数。 然后,我们需要实现一个切换案例系统,以检查正确的操作并对该状态进行重新计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // src/app/store/reducer.ts import { CartActionTypes, CartActions } from"./actions"; export let initialState = [] export function reducer(state=initialState, action: CartActions) { switch (action.type) { case CartActionTypes.ADD_PRODUCT: return [...state, action.payload] case CartActionTypes.REMOVE_PRODUCT: let product = action.payload return state.filter((el)=>el.id != product.id) default: return state } } |
这是我们的减速器功能。 请记住,我们不想使用状态更改方法。 不容忍是这里的关键。
我确定您想知道上面代码中的某些对象,例如
初始化商店
目前,我们在此应用中只有一个状态,因此只有一个" getter"减速器。 您可以在此处看到演练,每个状态项都有其自己的reducer功能。 在一个大型而复杂的应用程序中,我们可以有多个化简器,该应用程序中的每个状态项都可以使用一个化简器。 这些减速器将使用
现在,让我们的减速器进入商店:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // app.module.ts ... import { StoreModule } from"@ngrx/store"; import { reducer } from './store/reducer'; ... @NgModule({ declarations: [ ... ], imports: [ ... StoreModule.forRoot({cart: reducer}) ], providers: [], bootstrap: [AppComponent] }); export class AppModule { } |
在这里,我们使应用程序的商店可用于整个应用程序。 我们可以在任何地方访问它。 查看代码,我们从@ ngrx / store导入了
我们传入了设置了
投射状态
现在,我们已经使商店可访问,我们可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // app.componen.ts import { Component } from '@angular/core'; import { Store, select } from '@ngrx/store'; @Component({ selector: 'app-root', template: ` <p> app works !! Cart: {{cart.length}} <router-outlet></router-outlet> </p> `, styles: [] }) export class AppComponent { title = 'app'; cart: Array<any> constructor(private store: Store<any>) {} ngOnInit() { // Called after the constructor, initializing input properties, and the first call to ngOnChanges. // Add 'implements OnInit' to the class. this.store.select('cart').subscribe((state => this.cart = state)) } } |
设置动作
操作是对应用商店的查询。 他们"分派"要在商店上执行的动作。
首先,我们需要一个模型。 我们状态的基础是一系列产品。 数组中的产品代表用户可以购买的商品。 用户可以将其添加到
因此,我们知道我们的购物车如何包含产品/物品,并且产品可以具有以下内容:
名称
价钱
标签
ID
描述
在这里,我们仅选择
接下来,我们将为我们的产品创建一个模型:
1 2 3 4 5 6 7 | // src/app/store/product.model.ts export class Product { id: number name: string price: number } |
接下来,我们将动作定义为实现@ ngrx / store
而不是调度这样的动作:
1 | store.dispatch({type: '', payload: ''}) |
我们将动作创建为新的类实例:
1 | this.store.dispatch(new Cart.AddProduct(product)) |
将动作表示为类可以在减速器功能中进行类型检查。 要创建我们的动作类,首先我们创建一个
1 2 3 4 5 6 | // src/app/store/actions.ts export enum CartActionTypes { ADD_PRODUCT = 'ADD_PRODUCT', REMOVE_PRODUCT = 'REMOVE_PRODUCT' } |
注意:您需要先创建
现在,通过实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // src/app/store/actions.ts import { Action } from '@ngrx/store' ... export class AddProduct implements Action { readonly type = CartActionTypes.ADD_PRODUCT constructor(public payload: any){} } export class RemoveProduct implements Action { readonly type = CartActionTypes.REMOVE_PRODUCT constructor(public payload: any){} } |
1 | new Cart.AddProduct(product) |
最后,我们将为上面定义的所有操作定义一个
1 2 3 4 | // src/app/store/actions.ts ... export type CartActions = AddProduct | RemoveProduct |
这是完整的动作代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // src/app/store/actions.ts import { Action } from '@ngrx/store' export enum CartActionTypes { ADD_PRODUCT = 'ADD_PRODUCT', REMOVE_PRODUCT = 'REMOVE_PRODUCT' } export class AddProduct implements Action { readonly type = CartActionTypes.ADD_PRODUCT constructor(public payload: any){} } export class RemoveProduct implements Action { readonly type = CartActionTypes.REMOVE_PRODUCT constructor(public payload: any){} } export type CartActions = AddProduct | RemoveProduct |
设置组件
我认为我们现在已完成设置商店的工作! 现在,让我们看看如何在我们的组件中利用它们。
我们已经创建了应用程序中需要的所有组件。
这将显示产品列表。 对于本文,我们将对产品列表进行硬编码。 您可以扩展此应用程序以从资源中加载产品,但我们将由您自己决定。
为了对我们的产品列表进行硬编码,我们将创建一个保存我们的产品列表的文件。 我们将创建一个
1 | $ touch src/app/store/market.ts |
接下来,我们将初始化类型为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // src/app/store/market.ts import { Product } from"./product.model"; export const PRODUCTS: Product[] = [ { id: 0, name:"HP Inspirion", price: 700 }, { id: 1, name:"MacBook Pro 2018", price: 15000 }, { id: 2, name:"Dell 5500", price: 3000 } ] |
现在,我们可以在需要的任何地方导入
为了显示
1 2 3 4 | // src/app/products/products.component.ts import { PRODUCTS } from"./../store/market"; ... |
接下来,我们将其分配给
1 2 3 4 5 6 7 8 9 10 11 | // src/app/products/products.component.ts ... export class ProductsComponent implements OnInit { products = PRODUCTS constructor() { } ngOnInit() { } } |
我们将制作一个漂亮的HTML来显示我们的产品列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // src/app/products/products.component.ts ... @Component({ selector: 'app-products', template: ` <div class="col-lg-3 col-md-3 col-sm-6 col-xs-12" *ngFor="let product of products"> <div class="my-list"> <img src="http://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" /> <h3>{{product.name}}</h3> <span>$</span> <span class="pull-right">{{product.price}}</span> <div class="offer">Extra 5% Off. Cart value $ {{0.5 * product.price}}</div> <div class="detail"> <p>{{product.name}} </p> <img src="http://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" /> <a [routerLink]="['/product',product.id]" class="btn btn-info">View</a> </div> </div> `, styles: [ ] }) ... |
我们使用
将所有内容放在一起,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // src/app/products/products.componenet.ts import { Component, OnInit } from '@angular/core'; import { PRODUCTS } from"./../store/market"; @Component({ selector: 'app-products', template: ` <div class="col-lg-3 col-md-3 col-sm-6 col-xs-12" *ngFor="let product of products"> <div class="my-list"> <img src="http://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" /> <h3>{{product.name}}</h3> <span>$</span> <span class="pull-right">{{product.price}}</span> <div class="offer">Extra 5% Off. Cart value $ {{0.5 * product.price}}</div> <div class="detail"> <p>{{product.name}} </p> <img src="http://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" /> <a [routerLink]="['/product',product.id]" class="btn btn-info">View</a> </div> </div> `, styles: [ ] }) export class ProductsComponent implements OnInit { products = PRODUCTS constructor() { } ngOnInit() { } } |
接下来,打开" src / app / styles.scss"并添加以下SCSS代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | // src/app/styles.scss ... img { max-width: 100%; } img { transition: all .5s ease; -moz-transition: all .5s ease; -webkit-transition: all .5s ease } .my-list { width: 100%; padding: 10px; border: 1px solid #f5efef; float: left; margin: 15px 0; border-radius: 5px; box-shadow: 2px 3px 0px #e4d8d8; position: relative; overflow: hidden; } .my-list h3 { text-align: left; font-size: 14px; font-weight: 500; line-height: 21px; margin: 0px; padding: 0px; border-bottom: 1px solid #ccc4c4; margin-bottom: 5px; padding-bottom: 5px; } .my-list span { float: left; font-weight: bold; } .my-list span:last-child { float: right; } .my-list .offer { width: 100%; float: left; margin: 5px 0; border-top: 1px solid #ccc4c4; margin-top: 5px; padding-top: 5px; color: #afadad; } .detail { position: absolute; top: -100%; left: 0; text-align: center; background: #fff; height: 100%; width: 100%; } .my-list:hover .detail { top: 0; } |
这样做将使SCSS代码可用于我们所有的组件。
在这里,我们可以查看产品,查看其价格,名称,然后根据需要添加到购物车。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | // src/app/product/product.component.ts import { Component, OnInit } from '@angular/core'; import { PRODUCTS } from"./../store/market"; import { Product } from"./../store/product.model" import { ActivatedRoute } from"@angular/router"; import { Store } from"@ngrx/store"; import * as Cart from"./../store/actions"; @Component({ selector: 'app-product', template: ` <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> <div class="my-list"> <img src="http://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" /> <h3>{{product.name}}</h3> <span>$</span> <span class="pull-right">{{product.price}}</span> <div class="offer"> Extra 5% Off. Cart value $ {{0.5 * product.price}} </div> <div class="offer"> <a (click)="addToCart(product)" class="btn btn-info">Add To Cart</a> </div> </div> </div> `, styles: [ ] }) export class ProductComponent implements OnInit { product:Product constructor(private route: ActivatedRoute, private store: Store<any>) { } ngOnInit() { this.route.params.subscribe((p)=>{ let id = p['id'] let result = Array.prototype.filter.call(PRODUCTS,(v)=>v.id == id) if (result.length > 0) { this.product = result[0] } }) } addToCart(product) { this.store.dispatch(new Cart.AddProduct(product)) } } |
在这里,我们已经导入了
当执行
这将在我们的购物车中显示产品。 这是代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | // src/app/cart/cart.component.ts import { Component, OnInit } from '@angular/core'; import { Store, select } from"@ngrx/store"; import { Observable } from"rxjs/Observable"; import * as Cart from"./../store/actions"; @Component({ selector: 'app-cart', template: ` <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" *ngFor="let product of cart | async"> <div class="my-list"> <img src="http://hpservicecenterschennai.in/images/hp_laptop_service_centers_in_guindy.png" alt="" /> <h3>{{product.name}}</h3> <span>$</span> <span class="pull-right">{{product.price}}</span> <div class="offer"> Extra 5% Off. Cart value $ {{0.5 * product.price}} <a (click)="removeFromCart(product)" class="btn btn-info">Remove From Cart</a> </div> </div> </div> `, styles: [] }) export class CartComponent implements OnInit { cart: Observable<Array<any>> constructor(private store:Store<any>) { this.cart = this.store.select('cart') } ngOnInit() { } removeFromCart(product) { this.store.dispatch(new Cart.RemoveProduct(product)) } } |
我们声明了类型为
这是我们的最终组成部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // src/app/app.component.ts import { Component } from '@angular/core'; import { Store, select } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; @Component({ selector: 'app-root', template: ` <div class="container"> <div class="row"> <div class="col-sm-12"> <h1 class="text-center">Online Store</h1> <h6 class="text-center"><a [routerLink]="['/cart']">Cart: {{(cart | async).length}}</a></h6> <hr /> </div> </div> <router-outlet></router-outlet> </div> `, styles: [] }) export class AppComponent { title = 'app'; constructor(private store: Store<any>) {} cart: Observable<Array<any>> ngOnInit() { // Called after the constructor, initializing input properties, and the first call to ngOnChanges. // Add 'implements OnInit' to the class. this.cart = this.store.select('cart') } } |
您会看到我们已经修改了模板,以包含我们的应用程序"在线商店"的名称。 我们订阅了购物车商店以获取产品数量,然后使用
您将在单向数据流中看到
要查看我们已经完成的所有工作,请确保保存了每个文件并在终端中运行以下命令:
1 | $ ng serve |
瞧! 现在您可以使用该应用程序了。
利用AsyncPipe
AsyncPipe是一个内置管道,我们可以在模板中使用它们来解包
异步管道在组件中使用时,将其标记为要检查的更改。
查看执行此操作的组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // src/app/app.component.ts ... @Component({ selector: 'app-root', template: ` ... <h6 class="text-center"><a [routerLink]="['/cart']">Cart: {{(cart | async).length}}</a></h6> ... `, ... }) ... |
我们本可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // src/app/app.component.ts ... @Component({ selector: 'app-root', template: ` <div class="container"> <div class="row"> <div class="col-sm-12"> <h1 class="text-center">Online Store</h1> <h6 class="text-center"><a [routerLink]="['/cart']">Cart: {{cart.length}}</a></h6> <hr /> </div> </div> <router-outlet></router-outlet> </div> `, styles: [] }) export class AppComponent { title = 'app'; constructor(private store: Store<any>) {} cart: Array<any> ngOnInit() { // Called after the constructor, initializing input properties, and the first call to ngOnChanges. // Add 'implements OnInit' to the class. this.cart = this.store.select('cart') .subscribe(state => this.cart = state) } } |
您可以看到我们的代码变长了一点,但是仍然有效!
因此,"异步管道"为我们执行订阅,并将值附加到我们的模板中。 仅用几行代码即可完成大量工作。 完善!
使用Redux-DevTools进行调试
Redux-Devtools是用于测试UI状态的"时间旅行"调试工具。 它可以进行应用程序开发并提高开发效率。
使用这些工具,您可以从字面上移到应用程序的未来或过去状态(因此称为"时间旅行"说明)。
Redux-DevTools的"锁定"和"暂停"功能使您可以从历史记录中删除过去的操作或禁用它们。
我们可以在Angular应用中使用Redux-Devtools,但是ngrx团队开发了自己的dev-tool,可以在任何支持ngrx的应用中使用。
可以通过运行以下命令来安装它:
1 | $ npm i @ngrx/store-devtools -S |
这是下载Redux-DevTools扩展的链接。
将
1 2 3 4 5 6 7 8 9 10 11 | import { StoreDevtoolsModule } from '@ngrx/store-devtools' @NgModule({ imports: [ StoreDevtoolsModule.instrumentOnlyWithExtension({ maxAge: 6 }) ] }) export AppModule() {} |
我们将不讨论有关如何使用Redux-DevTools的技术细节。 相反,您可以将其作为一项任务来完成,方法是将其添加到该项目中,以提高自己,我希望收到您的来信! 随时向我发送PR!
资源资源
想更多地了解Angular,TypeScript和其他有用的前端库吗? 我建议您查看一些更详细的资源,例如在线课程:
Angular 5-完整指南
角与角材料,Angularfire和NgRx
了解TypeScript
结论
我知道,我们违反了几种最佳做法,但是最重要的是,您已经了解了如何使用@ ngrx / store来构建Angular应用程序。
如果您迷路或需要参考代码,可以对其进行分叉或从我的GitHub存储库中克隆它。 您可以扩展该应用程序以执行我没有想到的事情。 随便玩吧。