手把手教你用ngrx管理Angular状态吃的快不吐骨头
本文将与你一起探讨如何用不可变数据储存的方式进行Angular应用的状态管理:ngrx/store——Angular的响应式Redux。本文将会完成一个小型简单的Angular应用,最终代码可以在这里下载。
Angular应用中的状态管理
近几年,大型复杂Angular/AngularJS项目的状态管理一直是个让人头疼的问题。在AngularJS(1.x版本)中,状态管理通常由服务,事件,$rootScope混合处理。在Angular中(2+版本),组件通信让状态管理变得清晰一些,但还是有点复杂,根据数据流向不同会用到很多方法。
注意:本文中,AngularJS特指1.x版本,Angular对应2.0版本及其以上。
有人用Redux来管理AngularJS或者Angular的状态。Redux是JavaScript应用的可预测状态容器,支持单一不可变数据储存。Redux最有名的就是结合React的使用,当然它可以用于任意的视图层框架。Egghead.io发布了一份非常优质的Redux免费视频教程,视频由Redux作者DanAbramov本人讲解。
初识ngrx/store
本文将采用ngrx/store管理我们的Angular应用。那么,ngrx/store和Redux什么关系呢?为什么不用Redux呢?
与Redux的关系
ngrx/store中的基本原则
State(状态)是指单一不可变数据
Action(行为)描述状态的变化
Reducer(归约器/归约函数)根据先前状态以及当前行为来计算出新的状态
状态用State的可观察对象,Action的观察者——Store来访问
我们会详细解释说明。先快速过一遍基础,然后在实战的过程中慢慢深入解释。
Actions(行为)
Actions是信息的载体,它发送数据到reducer,然后reducer更新store。Actions是store能接受数据的唯一方式。
在ngrx/store里,Action的接口是这样的:
//actions包括行为类型和对应的数据载体exportinterfaceAction{type:string;payload:any;}
type描述我们期待的状态变化类型。比如,添加待办'ADD_TODO',增加'DECREMENT'等。payload是发送到待更新store中的数据。store派发action的代码类似如下:
//派发action,从而更新storestore.dispatch({type:'ADD_TODO',payload:'Buymilk'});
Reducers(归约器)
Reducers规定了行为对应的具体状态变化。它是纯函数,通过接收前一个状态和派发行为返回新对象作为下一个状态的方式来改变状态,新对象通常用Object.assign和扩展语法来实现。
//reducer定义了action被派发时state的具体改变方式exportconsttodoReducer=(state=[],action)=>{switch(action.type){case'ADD_TODO':return[...state,action.payload];default:returnstate;}}
开发时特别要注意函数的纯性。因为纯函数:
不会改变它作用域外的状态
输出只决定于输入
相同输入,总是得到相同输出
关于函数的纯性,可以点击这里进一步了解。开发时,要确保函数的纯性和状态不可变性,所以写reducers的时候要多加小心。
Store(存储)
store中储存了应用中所有的不可变状态。ngrx/store中的store是RxJS状态的可观察对象,以及行为的观察者。
我们可以利用Store来派发行为。当然,我们也可以用Store的select()方法获取可观察对象,然后订阅观察,在状态变化之后做出反应。
ngrx/store实战:个性宠物标签
目前我们熟悉了ngrx/store的基本工作原理,接下来我们来开发一个能让用户自定义宠物名称标签的应用。该应用将会有以下功能:
用户可以选择标签形状,字体,文案,以及附加特性
创建过程可以预览标签效果
完成后,可以继续创建
完成后的个性宠物标签app效果如下:
让我们开始吧!
Angular应用设置
安装依赖
确保你已经安装了NodeJS,推荐LTS版本。
用npm安装AngularCLI包,方便一键生成项目手脚架。运行以下命令来全局安装angular-cli。
$npminstall-g@angular/cli
创建项目
选好项目所在的文件夹,打开命令行,输入以下命令来创建一个新的Angular项目:
$ngnewpet-tags-ngrx
进入新创建的文件夹,安装必要的包:
$cdpet-tags-ngrx$npminstall@ngrx/core@ngrx/store--save
一切准备就绪,可以开始开发了。
定制你的项目模板
让我们根据这个项目的需求,稍微改造一下项目模板。
创建src/app/core文件夹
首先,创建文件夹src/app/core。应用的根组件和核心文件都会放在这个文件夹下。将所有的app.component.*文件移动到这里。
更新App模块
接着,打开src/app/app.module.ts文件,更新app.component的路径:
//src/app/app.module.ts...import{AppComponent}from'./core/app.component';...
静态资源整理
定位到src/assets文件夹。
在assets文件夹下新建一个images的文件夹,稍后我们会添加一些图片。然后,将根目录下的src/styles.css移动到src/assets下。
styles.css的移动需要我们修改.angular-cli.json的配置。打开这个文件,把styles属性改成如下:
//.angular-cli.json..."styles":["assets/styles.css"],...
集成Bootstrap
最后,在index.html中添加Bootstrap样式。在标签上加上CDN地址。这里我们只用到样式,不需要脚本文件。顺便,更新一下标题,变成CustomPetTags:
启动服务
我们可以在本地起个服务,然后监听文件变化实时更新:
$ngserve
App组件
现在开始创建新功能。从根组件app.component.*入手。不要担心,变化很小。
删除样式文件
删除app.component.css文件。该组件只用Bootstrap来定义样式,所以不需要额外样式。
根组件脚本
在app.component.ts文件中删除对上述样式文件的引用。我们也可以删除AppComponent类中的title属性。
//src/app/core/app.component.tsimport{Component}from'@angular/core';@Component({selector:'app-root',templateUrl:'./app.component.html'})exportclassAppComponent{}
根组件模版
在app.component.html中添加一些内容,变成如下:
CustomPetTags
我们用Bootstrap来添加珊格系统和标题。然后添加一个指令,这是当这个单页面应用中添加路由后,视图会渲染的地方。到现在为止,程序会报错。等我们建好了路由和page组件的时候,就好了。
创建页面组件
我们先创建好各页面手脚架,以便搭建路由。然后再回来完善各个组件。
在根目录下运行如下指令创建页面组件:
$nggcomponentpages/home$nggcomponentpages/create$nggcomponentpages/complete
ngg命令可以快速生成组件,指令,过滤器和服务,同时也会自动把生成的文件导入到app.module.ts中。现在,我们有三个页面组件的脚手架,可以开始搭建路由了。
搭建路由
新建一个路由模块,在src/app/core文件夹下创建一个app-routing.module.ts文件:
//src/app/core/routing-module.tsimport{NgModule}from'@angular/core';import{RouterModule}from'@angular/router';import{HomeComponent}from'../pages/home/home.component';import{CreateComponent}from'./../pages/create/create.component';import{CompleteComponent}from'./../pages/complete/complete.component';@NgModule({imports:[RouterModule.forRoot([{path:'',component:HomeComponent},{path:'create',component:CreateComponent},{path:'complete',component:CompleteComponent},{path:'**',redirectTo:'',pathMatch:'full'}])],providers:[],exports:[RouterModule]})exportclassAppRoutingModule{}
现在有三个路由/,/create,/complete,未知路由会重定向到首页。
打开根模块文件app.module.ts,添加新增路由模块AppRoutingModule至imports属性。
//src/app/app.module.ts...import{AppRoutingModule}from'./core/app-routing.module';@NgModule({...,imports:[...,AppRoutingModule],...
到此,路由设置完毕。我们可以通过路由来访问不同页面,访问首页的时候,HomeComponent就会渲染在所在的位置,如下图所示:
“Home”页面组件
现在,让我们添加提示信息和跳转到/create页面的按钮。打开home.component.html,替换内容如下:
Pleasesignuporlogintocreateacustomnametagforyourbelovedpet!
LogIn
现在,首页效果如下:
宠物标签模型
开始实现个性宠物标签生成器功能和状态管理的工作了。首先,为我们的状态创建一个数据模型,该模型描述了当前的宠物标签。
新建文件src/app/core/pet-tag.model.ts:
//src/app/core/pet-tag.model.tsexportclassPetTag{constructor(publicshape:string,publicfont:string,publictext:string,publicclip:boolean,publicgems:boolean,publiccomplete:boolean){}}exportconstinitialTag:PetTag={shape:'',font:'sans-serif',text:'',clip:false,gems:false,complete:false};
宠物标签行为
现在可以创建行为类型了。回顾之前说的,action被派发到reducer中,从而更新store。现在为我们想要的每种行为定义名字。
创建文件src/app/core/pet-tag.actions.ts
//src/app/core/pet-tag.actions.tsexportconstSELECT_SHAPE='SELECT_SHAPE';exportconstSELECT_FONT='SELECT_FONT';exportconstADD_TEXT='ADD_TEXT';exportconstTOGGLE_CLIP='TOGGLE_CLIP';exportconstTOGGLE_GEMS='TOGGLE_GEMS';exportconstCOMPLETE='COMPLETE';exportconstRESET='RESET';
将行为定义为常量。我们也可以构造可注入的行为类,就像ngrx/example-app中那样。但我们这个例子很简单,用这种方法反而会增加复杂度。
宠物标签归约器
现在可以创建我们的归约函数了,这个函数接受action,更新store。
新建文件src/app/core/pet-tag.reducer.ts:
//src/app/core/pet-tag.reducer.tsimport{Action}from'@ngrx/store';import{PetTag,initialTag}from'./../core/pet-tag.model';import{SELECT_SHAPE,SELECT_FONT,ADD_TEXT,TOGGLE_CLIP,TOGGLE_GEMS,COMPLETE,RESET}from'./pet-tag.actions';exportfunctionpetTagReducer(state:PetTag=initialTag,action:Action){switch(action.type){caseSELECT_SHAPE:returnObject.assign({},state,{shape:action.payload});caseSELECT_FONT:returnObject.assign({},state,{font:action.payload});caseADD_TEXT:returnObject.assign({},state,{text:action.payload});caseTOGGLE_CLIP:returnObject.assign({},state,{clip:!state.clip});caseTOGGLE_GEMS:returnObject.assign({},state,{gems:!state.gems});caseCOMPLETE:returnObject.assign({},state,{complete:action.payload});caseRESET:returnObject.assign({},state,initialTag);default:returnstate;}}
首先从ngrx/store导入Action。同时也需要PetTag数据模型以及它的初始状态initialTag。还有上一步中创建的行为类型也需要导入。
然后创建petTagReducer()函数,该函数接收两个参数:上一个状态state和被派发的行为action。注意它是输入决定输出的纯函数,函数不会改变全局的状态。这就是说,从归约器返回的数据要么是新对象,要么是未修改的输入,比如default情况。
通常,我们可以借用Object.assign()从输入数据中得到全新的对象。输入数据是上一个状态以及包含行为载体(payload)的对象。
TOGGLE_CLIP和TOGGLE_GEMS切换initialTag状态中的布尔值,所以当我们派发这两种行为的时候,不需要行为载体,我们只需要简单取反即可。
COMPLETE行为需要一个载体,因为我们明确要将其设置为true,而且每个标签只能操作一次。我们也可以切换布尔值,但明确起见,我们还是会派发一个具体的值作为行为载体。
注意:注意RESET行为用到导入的initialTag。因为它是个不变量,所以在这里使用并不会违背归约函数的纯性。
根模块导入Store
完成了行为和归约函数的定义之后,我们要告诉应用程序有这些的存在。打开app.module.ts文件,更新如下:
//src/app/app.module.ts...import{StoreModule}from'@ngrx/store';import{petTagReducer}from'./core/pet-tag.reducer';@NgModule({...,imports:[...,StoreModule.provideStore({petTag:petTagReducer})],...
现在,我们可以用Store来实现状态管理了。
创建“Create”页面
之前创建的CreateComponent是个智能组件(SmartComponent),它会有几个木偶子组件(DumbComponent)。
智能组件/木偶组件
智能组件也称容器组件,通常作为根级组件,包含业务逻辑,状态管理,订阅,处理事件。在这个例子中,就是那些可路由的页面组件。CreateComponent是智能组件,它将为标签生成器制定业务逻辑。同时,它会处理木偶子组件触发的事件,而这些子组件是标签生成器的一部分。
木偶组件又名展示组件,它只决定于父组件传递的数据。它可以触发事件,然后在父组件中处理,但它不会直接影响订阅或者store。木偶组件是可复用的模块化组件。比如,我们会同时在Create页面和Complete页面使用标签预览这个木偶组件(CreateComponent和CompleteComponent是智能组件)。
“Create”页面功能点
Create页面将会有以下几个功能:
标签形状选择
标签字体选择和文案输入
是否添加clip和gems
标签形状和文案的预览
结束操作的完成按钮
“Create”组件脚本
我们先从CreateComponent开始,打开文件create.component.ts:
这个智能组件主要作用为自定义宠物标签。
引入OnInit以及OnDestroy,分别用于初始化和销毁订阅。同时,需要从RxJS中引入Observable和Subscription,从ngrx/store引入Store对象。由于行为基本都在这个组件中派发,所以需要引入之前定义好的所有行为(除RESET外)。最后,引入PetTag数据模型。
该组件不需要额外的样式,所以删除CSS文件以及对它的引用。
该类中,tagState$定义为PetTag数据类型的可观察对象,通过构造器中用store的select()方法赋值实现。
在ngOnInit()钩子函数中,将subscription(订阅)设置为对tagState$可观察对象的订阅。每当有新状态生成时,订阅就会把petTag设置为可观察对象流返回的新状态state。done属性用来检查shape和text是否已经填写。这两个属性是标签完成的必填项。当组件销毁的时候,ngOnDestroy()钩子函数被触发,执行销毁订阅。
最后,创建派发行为至store的事件处理函数。当子木偶组件触发事件来更新状态时,这些事件处理函数就会执行。每个函数都用store.dispatch()派发期望的行为类型type和行为载体payload至归约函数。
注意:在更复杂的应用中,你可能希望在单独的服务中派发行为,然后注入到组件中。不过,现在我们这个仅仅为学习而创建的小应用,没有必要这么做。直接在智能组价中派发行为就行了。
形状组件
开始创建我们的第一个展示组件:TagShapeComponent。当该组件完成时,创建页面预期效果如下:
用AngularCLI命令一键生成这个子组件的脚手架:
$nggcomponentpages/create/tag-shape
标签形状组件将会展示四种不同的形状图片:骨头形,方形,圆形,心形。用户可以从中选择喜欢的形状。
从git仓库下载图片,放置在pet-tags-ngrx/src/assets/images目录下。
形状组件脚本
打开tag-shape.component.ts文件:
//src/app/pages/create/tag-shape/tag-shape.component.tsimport{Component,Output,EventEmitter}from'@angular/core';@Component({selector:'app-tag-shape',templateUrl:'./tag-shape.component.html',styleUrls:['./tag-shape.component.css']})exportclassTagShapeComponent{tagShape:string;@Output()selectShapeEvent=newEventEmitter();constructor(){}selectShape(shape:string){this.selectShapeEvent.emit(shape);}}
从@angular/core引入Output和EventEmitter。
形状选择用radio按钮表示,所以需要一个属性来储存形状。由于形状是用字符串来描述的,我们将tagShape的类型设置为string。
当用户选择某个形状之后,我们需要装饰器@Output来触发事件。并且发送信息至父组件CreateComponent。selectShape(shape)函数会触发携带形状信息的事件,然后父组件用先前在CreateComponent定义的selectShapeHandler()去处理。稍后我们就可以看到父子组件共同工作的效果。
形状组件模版
在那之前,先让我们来修改一下TagShapeComponent的模版内容。打开文件tag-shape.component.html,修改如下。
Shape
Chooseatagshapetogetstarted!
创建四个radio按钮分别对应四个形状的图片。无论选择哪个,都会触发(change)事件,然后触发携带tagShape参数的selectShapeEvent事件。
形状组件样式
打开tag-shape.component.css文件,添加样式如下:
/*src/app/pages/create/tag-shape/tag-shape.component.css*/:host{display:block;margin:20px0;}.tagShape{padding:10px;text-align:center;}img{display:block;height:auto;margin:0auto;max-height:50px;max-width:100%;width:auto;}
添加形状组件至Create页面
最后,将TagShapeComponent添加至智能组件CreateComponent模版中,我们就算完成了。打开create.component.html文件,替换如下:
Hello!Createacustomizedtagforyourpet.
父组件现在能监听到来自子组件的selectShapeEvent事件,同时通过执行之前在CreateComponent中定义的selectShapeHandler()函数来处理该事件。回忆一下,这个函数派发了SELECT_SHAPE行为至store:
selectShapeHandler(shape:string){this.store.dispatch({type:SELECT_SHAPE,payload:shape});}
现在,应用可以在用户选择形状的时候更新状态了。
文字组件
现在我们来创建用户输入字体和文字的组件。完成后,页面期待效果如下:
用命令行来创建该组件的手脚架:
$nggcomponentpages/create/tag-text
文字组件脚本
打开tag-text.component.ts文件,修改如下:
//src/app/pages/create/tag-text/tag-text.component.tsimport{Component,Output,EventEmitter}from'@angular/core';@Component({selector:'app-tag-text',templateUrl:'./tag-text.component.html',styleUrls:['./tag-text.component.css']})exportclassTagTextComponent{tagTextInput='';fontType='sans-serif';@Output()selectFontEvent=newEventEmitter;@Output()addTextEvent=newEventEmitter;constructor(){}selectFont(fontType:string){this.selectFontEvent.emit(fontType);}addText(text:string){this.addTextEvent.emit(text);}}
该组件跟上一个组件TagShapeComponent工作方式相同,所以代码也差不多。引入Output和EventEmitter,并且创建tagTextInput和fontType属性来记录用户的输入。
当用户修改字体或者文字时,组件就会触发事件让父组件捕获。
文字组件模板
标签文字组件模板tag-text.component.html代码如下:
Text
Selectyourdesiredfontstyleandenteryourpet'sname.
Youcanseewhatyourtagwilllooklikeinthepreviewbelow.
Font:Sans-serifSerifText:我们用
文字组件样式
我们只需在tag-text.component.css中添加一个样式:
/*src/app/pages/create/tag-text/tag-text.component.css*/:host{display:block;margin:20px0;}
添加文字组件至Create页面
最后将TagTextComponent组件添加到Create页面:
...
注意我们在元素上加了*ngIf结构指令,我们希望用户在选择了标签形状之后才显示该组件。我们即将创建标签预览组件,没有标签形状的预览没有什么意义,*ngIf保证了这点。
在父组件中监听TagTextComponent的selectFontEvent和addTextEvent事件,然后用之前在CreateComponent定义好的方法去处理。处理方式分别为派发SELECT_FONT和ADD_TEXT以及对应的行为载体(payload)至归约器(reducer):
selectFontHandler(fontType:string){this.store.dispatch({type:SELECT_FONT,payload:fontType});}addTextHandler(text:string){this.store.dispatch({type:ADD_TEXT,payload:text});}
附加特性组件
现在,我们来添加能让用户额外选择的几个特性。完成之后,创建页面预期效果如下:
同样用命令来创建TagExtrasComponent组件的手脚架:
$nggcomponentpages/create/tag-extras
附加特性组件脚本
打开tag-extras.component.ts,修改如下:
//src/app/pages/create/tag-extras/tag-extras.component.tsimport{Component,Output,EventEmitter}from'@angular/core';@Component({selector:'app-tag-extras',templateUrl:'./tag-extras.component.html',styleUrls:['./tag-extras.component.css']})exportclassTagExtrasComponent{tagClip:boolean;gems:boolean;@Output()toggleClipEvent=newEventEmitter;@Output()toggleGemsEvent=newEventEmitter;constructor(){}toggleClip(){this.toggleClipEvent.emit();}toggleGems(){this.toggleGemsEvent.emit();}}
代码看起来应该很熟悉了吧?!额外选项包括是否添加clip和gems,所以这两个参数是boolean类型。
附加特性组件模板
打开tag-extras.component.html,添加代码如下:
Extras
Selectanyextrasyouwouldliketoadd.
用checkbox让用户来勾选是否要添加额外特性。
附加特性组件样式
由于该组件是最后一个标签编辑组件,我们希望在底部添加边界线,打开tag-extras.component.css文件,添加如下:
/*src/app/pages/create/tag-extras/tag-extras.component.css*/:host{border-bottom:1pxsolid#ccc;display:block;margin:20px0;padding-bottom:20px;}
添加附加特性组件至Create页面
在create.component.html文件中添加如下代码:
...
跟标签文字组件一样,我们只在用户选择了标签形状之后才显示附加特性选项。toggleClipEvent和toggleGemsEvent事件被之前在CreateComponent定义好的方法处理,处理方式分别为派发TOGGLE_CLIP和TOGGLE_GEMS行为至归约器:
toggleClipHandler(){this.store.dispatch({type:TOGGLE_CLIP});}toggleGemsHandler(){this.store.dispatch({type:TOGGLE_GEMS});}
由于选择的切换是布尔值,不需要行为载体。这种情况,我们只需要在归约器中使用上一个状态来决定下一个状态。
预览组件
现在让我们来创建标签预览组件,预期效果如下:
用命令行来创建TagPreviewComponent组件的手脚架,它将是Create页面和Complete页面的子组件,所以把这个组件放在app文件夹下:
$nggcomponenttag-preview
预览组件脚本
打开tag-preview.component.ts文件,修改如下:
//src/app/tag-preview/tag-preview.component.tsimport{Component,OnChanges,Input}from'@angular/core';import{PetTag}from'./../core/pet-tag.model';@Component({selector:'app-tag-preview',templateUrl:'./tag-preview.component.html',styleUrls:['./tag-preview.component.css']})exportclassTagPreviewComponentimplementsOnChanges{@Input()petTag:PetTag;imgSrc='';tagClipText:string;gemsText:string;constructor(){}ngOnChanges(){this.imgSrc=`/assets/images/${this.petTag.shape}.svg`;this.tagClipText=this.boolToText(this.petTag.clip);this.gemsText=this.boolToText(this.petTag.gems);}privateboolToText(bool:boolean){returnbool'Yes':'No';}}
TagPreviewComponent是接受父组件CreateComponent输入的木偶子组件,它并不向父组件传递数据。引入Input装饰器以及OnChanges生命周期函数钩子,同时需要引入PetTag表明输入的类型。
TagPreviewComponent类需要实现OnChanges接口,也就是在该类中调用ngOnChanges方法。每当组件的输入参数发生变化时,ngOnChanges就会执行。这样才能实现在用户编辑修改的时候实时预览效果。
从父组件中接受的数据@Input()petTag是个状态对象,数据类型是之前定义的PetTag。比如,一个petTag对象可能是这样的:
{shape:'bone',font:'serif',text:'Fawkes',clip:true,gems:false,complete:false}
我们希望友好直观地展示数据,所以我们将会显示标签的形状图片,文案,以及是否包含了clip和gems。
当用户进行操作时,需要专门设置一下图片的路径以及clip和gems的文案(Yes或者No)。输入是由CreateComponent对tagState$这个可观察对象的订阅提供。
预览组件模版
打开tag-preview.component.html文件,添加代码如下:
{{petTag.text}} Tagclip:{{tagClipText}}
Gems:{{gemsText}}预览将会在用户选择标签形状之后显示。用一个shape样式类来控制显示正确的形状图片,同时也会显示标签的文字,字体是用font样式类来控制的。最后,直接标注出用户是否选择了包含clip,gems两个特性。
预览组件样式
之前我们定义4个标签形状:骨头形,方形,圆形,心形。为了优雅地展示预览效果,我们需要额外的样式。打开tag-preview.component.css样式,添加如下:
/*src/app/tag-preview/tag-preview.component.css*/.tagView-wrapper{padding-top:20px;}.tagView{height:284px;position:relative;width:100%;}img{display:block;height:100%;margin:0auto;width:auto;}.text{font-size:48px;position:absolute;text-align:center;text-shadow:1px1px0rgba(255,255,255,.8);top:99px;width:100%;}.bone.text,.rectangle.text{font-size:74px;top:85px;}.sans-serif{font-family:Arial,Helvetica,sans-serif;}.serif{font-family:Georgia,'TimesNewRoman',Times,serif;}
除了一些定位之外,我们根据不同形状设置了不同的字体大小,根据用户选择设置不同字体。
现在,我们可以添加到Create页面了。
将预览组件添加至Create页面
打开create.component.htm,将该组件添加至最底部:
...
方括号[...]单向绑定属性,我们在CreateComponen组件的订阅tagStateSubscription中已经创建了petTag,现在将其传递给预览组件。
现在,我们应该可以看到标签的实时预览效果了:
提交完成标签
我们已经在CreateComponent中创建了submit()方法,该方法派发带有载体的COMPLETE行为至归约器。我们只需要在create.component.html页面创建一个调用该方法的按钮即可:
...Previewyourcustomizedtagabove.
Ifyou'rehappywiththeresults,
clickthebuttonbelowtofinish!
Done
我们之前在CreateComponent的tagStateSubscription对象中定义了done属性。当done属性是false的时候,我们禁用提交按钮。
this.done=!!(this.petTag.shape&&this.petTag.text);
当标签有形状和文字时,我们就认为该标签已经可以提交了。如果用户已经添加了这些,他们就可以点击提交按钮完成标签的创建。同时,我们将用户导航至Complete页面。
“Complete”页面组件
在设置路由的时候,我们就搭建好了Complete页面的手脚架。当这个页面创建好之后,页面效果如下(前提是用户创建了一个标签):
“Complete”页面组件脚本
打开智能组件complete.component.ts,添加代码如下:
//src/app/pages/complete/complete.component.tsimport{Component,OnInit,OnDestroy}from'@angular/core';import{Observable}from'rxjs/Observable';import{Subscription}from'rxjs/Subscription';import{Store}from'@ngrx/store';import{RESET}from'./../../core/pet-tag.actions';import{PetTag}from'./../../core/pet-tag.model';@Component({selector:'app-complete',templateUrl:'./complete.component.html'})exportclassCompleteComponentimplementsOnInit,OnDestroy{tagState$:Observable;privatetagStateSubscription:Subscription;petTag:PetTag;constructor(privatestore:Store){this.tagState$=store.select('petTag');}ngOnInit(){this.tagStateSubscription=this.tagState$.subscribe((state)=>{this.petTag=state;});}ngOnDestroy(){this.tagStateSubscription.unsubscribe();}newTag(){this.store.dispatch({type:RESET});}}
CompleteComponent组件是可路由的容器组件。需要导入OnInit,OnDestroy,Observable,Subscription以及Store管理store订阅。同时,有个重置按钮,用户点击之后可以重新创建标签。store中的状态也会跟着重置,所以需要导入RESET行为,PetTag数据类型以及默认初始值initialTag。
这个组件不需要Bootstrap以外的样式,所以我们可以把样式文件complete.component.css以及相应的引用删掉。
类似CreateComponent组件,我们需要创建tagState$的可观察对象tagStateSubscription,以及局部变量petTag。同时,我们需要创建PetTag数据类型的emptyTag变量,然后将其赋值为initialTag。
在构造函数中,将tagState$设为store可观察对象。然后在ngOnInit()函数中,订阅这个可观察对象,并且设置petTag属性。在ngOnDestroy()函数中,通过退订来销毁订阅。最后,newTag()函数派发RESET行为,使得应用的状态重置,用户可以继续定制他们的下一个标签了。
“Complete”页面组件模板
CompleteComponent组件的模板代码如下:
Congratulations!You'vecompletedapetIDtagfor{{petTag.text}}.Wouldyouliketocreateanother Oops!Youhaven'tcustomizedatagyet.Clickheretocreateonenow. 首先展示恭喜用户成功为他们的宠物创建个性标签的提示,名字也会从petTag状态对象中取过来。同时,提供一个能重新创建新标签的链接,点击之后执行newTag()方法,该方法会将路由导航到创建页面重新开始。
接着,展示带有petTag的标签预览组件:。
最后,如果用户在没有完成标签的定制下手动导航到/complete页面的话,我们就会显示一个错误信息。同样有个链接,可以让用户回到创建页面。错误页面效果如下:
至此,我们简单的Angular+ngrx/store应用完成了。
题外话:你可能不需要ngrx/store
这个例子很简单,因为我们是用ngrx/store来教学。当你想用来投入实际项目时,你需要权衡一下必要性以及它的利弊。Angular(吸纳了RxJS)已经可以很方便地用service管理全局状态。因此,小型简单应用用局部变量就能很好地维护了。这种场景下,如果引入非必要的ngrx/store,可能会带来困扰和麻烦。
而在管理大型复杂项目的状态时,ngrx/store及其同类库是非常出色的工具。希望你现在已经有能力判断Redux和ngrx/store使用的原理。这样你就能知道如何以及何时应该使用状态管理库了。
附上一些学习状态管理的好资源:
ngrx/storeonGitHub
@ngrx/storein10minutes
ComprehensiveIntroductionto@ngrx/store
ng-conf:ReactiveAngular2withngrx-RobWomald
Angular2ServiceLayers:Redux,RxJSandNgrxStore-WhentoUseaStoreandWhy
GettingStartedwithRedux-DanAbramovonEgghead.io
Angular的服务和组件通信在小型应用中的数据传递变得相当简单,但在复杂应用中仍是个棘手的问题。类似ngrx/store的全局store在组织状态管理中起到了很大的辅助作用。希望你现在已经准备好用ngrx/store构建自己的Angular应用了!
THE END
1.宠物问诊app排行榜前十名偏玩手游盒子分享十大宠物问诊app排行榜前十名手机应用,编辑为您推荐手机宠物问诊app排行榜第一名到前5名到前十名的应用。找宠物问诊app有哪些、宠物问诊app哪个好用,上偏玩手游盒子https://m.pianwan.com/s/zj-2315764
2.宠物Appapp软件排行榜前十名好用的宠物Appapp软件版推荐宠物Appapp软件排行榜20 APP应用 5523 浏览 火热 热度多特手游网【宠物Appapp软件排行榜】榜单为玩家们带来好玩的 宠物Appapp软件排行榜游戏 ,里面有爱豆桌面宠物、华登宠物园、宠物圈、宠物众疗、等宠物Appapp软件排行榜 游戏,喜欢的玩家赶紧来看看多特手游网小编带来的2021超好玩的 宠物Appapp软件排行榜 游戏推荐...https://m.duote.com/top/rjtop4219/
3.养宠物app排行榜养宠物有没有养宠物的app多特手游网【养宠物app排行榜】榜单为玩家们带来好玩的 养宠物app排行榜游戏 ,里面有宠物情人、找宠物、点点宠物、全民养狗狗红包版、等养宠物app排行榜 游戏,喜欢的玩家赶紧来看看多特手游网小编带来的2021超好玩的 养宠物app排行榜 游戏推荐大全吧!总有一款适合你。https://mduote.runjiapp.com/top/ph730730/
4.养宠物必备app排行榜2024养宠物必备的软件推荐养宠物必备app有哪些?小编给大家推荐一些养宠物必备的软件,主要包括有宠物记、多仔宠物、宠物翻译模拟器等,APP内用户提供了各种养宠物的知识,还可以帮助你了解最新的宠物资讯,让你可以更好的记录宠物的成长,带给你美好的回忆,感兴趣的用户快来下载吧。http://m.doyo.cn/zhuanti/ycwbb/
1.宠物店10大品牌排行榜最新名单公布→十大品牌网入榜《2024年CNPP宠物店十大品牌榜中榜名录》的有:派多格、小佩、圣宠、袖虎、宠物家、萌小它、爪爪、极宠家、八公叔叔、宠儿宠物等,宠物店10大品牌排行榜由CNPP品牌榜中榜大数据「研究院」和CN10排排榜技术「研究院」通过资料收集整理,并基于大数据统计及人为根据市场和参数条件变化的分析研究专业测评而得出,是大数据...https://www.cnpp.cn/focus/36802.html
2.宠物服务APP功能详解:从降管理到社交互动宠物服务APP作为现代宠物主人管理宠物生活的重要工具,集成了多种功能以满足不同需求。以下是对宠物服务APP主要功能的详细解析,涵盖从健康管理到社交互动等多个方面。 一、健康管理 健康档案 用户可以在APP中创建宠物的个人档案,记录宠物的品种、年龄、性别、体重等基本信息。 https://guangzhou031261.11467.com/news/9052234.asp
3.AppStore上的“Buddy:狗监视器和宠物摄像头”提供App 内购买项目 截屏 Mac iPhone iPad Apple Watch 简介 Dog Monitor是一款深得宠物主人信赖的爱宠监护应用程序。 当您不在家的时候,您可以轻松地查看家里的宠物。这是和您的爱宠保持紧密连接的最直接的方式。采用两台设备 - 一台和宠物一起放在家里,另一台可以随身携带。有了Dog Monitor应用程序,就可以把您...https://itunes.apple.com/cn/app/id1046837813
4.管理生活软件哪个好管理生活应用app下载管理生活软件排行榜聚宠宠物app是一款专为喜欢宠物的用户打造的手机软件,聚宠宠物app里有着各种类型多样的养宠知识可以了解,聚宠宠物app和其他养宠用户在线进行分享探讨,让你养宠物可以更轻松。... [详情] 相关标签: 宠物狗 管理生活 狗狗品种 下载 领狗狗 7.2M | 生活助手 领狗狗app是一款生活服务应用,宠物狗领养网站——...https://www.zhuayoukong.com/softtags/31242.html
5.www.eazhsh.com/apf44416262.htmAPP_排行榜最高的视频APP_最火爆的应用APP下载 媚娘网_完整版的资源2022_茭白电影网 九九在线黄色视频在线 手机看片国产精品 37大但人文艺术_楚天都市报 历史剧《男生和女生一起差的轮滑鞋》超清免费在线观看 大肉棒视频 97碰碰人人色 美女被操到高潮在线观看 jul233 人妻少妇肛交拳交一区 亚洲国产成人...http://www.eazhsh.com/apf44416262.htm
6.养猫助手软件有哪些帮助我们养猫的软件推荐看猫宠物app下载,支持更好的养宠物,看猫宠物养宠交友社区可以让各种不同的优质宠物都能快速看到,科学养殖健康繁育更可靠,各种不同的可爱萌宠排行榜更有趣,多血统的宠物都能快速找到,想要更好的科学养宠赶紧来看猫宠物体验吧。 看猫宠物 评价:养宠互动交友社区 立即下载 15.宠物芯管家 宠物芯管家app下载,可以带...https://www.shubang.net/news/19387.html
7.宠物APP产品方向,宠物APP市场分析与定位随着人们生活质量的提高,养宠物已经成为人们丰富生活的一种方式。宠物管理app的开发迎合了大众的需求,为人们提供了养宠物的良好新体验。宠物管理app为这些主流群体提供专业的养护协助,让养宠物不再麻烦。接下来,app开发公司中阳科技的李经理和大家一起分析一下宠物管理app开发的三大真知灼见! https://www.isharead.cn/news-3864.html
8.全国十大宠物领养平台领养猫狗的平台推荐可领养宠物的app有哪些...领养宠物的意义在于,给流浪动物一个赖以生存的家,解决它们的温饱、健康、居住问题。那么,你知道全国宠物领养中心有哪些吗?下面买购网编辑就带大家看一份宠物领养平台名单,一起了解下宠安家、幸运土猫、宠物帮领养中心,以及有只宠物、宠胖胖、搜栖、宠友社等流浪猫/流浪狗领养平台。 https://m.maigoo.com/top/430609.html
9.养宠物赚钱app软件哪个最好用,养动物赚钱游戏app排行手机养宠物赚钱的软件与小程序哪个好,哪个赚的多?答案都在本专题里可以找到。这里收集的无数养宠物的app软件均可免费下载使用,并且都是官方正版,都是真的可以赚钱的,请君放心大胆的下载。https://www.nbegame.com/zhuanti/380.html
10.基于Uniapp+SSM宠物时光管理系统App论文宠物管家uniapp本文详细描述了一款结合Uniapp和SSM框架的宠物管理App,涉及技术架构、用户管理、宠物信息管理和订单管理模块,强调了跨平台开发、高效性能和优质用户体验的重要性。 摘要由CSDN通过智能技术生成 一、引言 随着移动互联网的普及和深入,人们越来越依赖于手机应用程序来管理日常生活和工作。其中,宠物作为家庭的重要成员,其管理...https://blog.csdn.net/qq_53797749/article/details/136017523
11.宠物管理软件医院护理用品商城寄养app定制开发第五步:收集客户反馈意见进行 其他: 关于赵小马定制开发多行业软硬件开发解决方案服务商,500强企业合作商赵小马网络科技是国内大型综合性,软件与信息化服务企业,依托十年的大型系统软件研发实力与丰富的项目管理经验,掌握各项软件开发技术,提高技能开发能力,帮助客户向数字化领域转型,打造智慧企业,持续为客户创造商业价值...https://www.zbj.com/fw/2149119.html
12.派可为智宠app下载派可为智宠(智能宠物管理神器)下载...派可为智宠(智能宠物管理神器)是派可为旗下一款宠物智能投喂管理软件,这款软件就类似于家庭打扫机器人一起设置程序,方便让主人出门的时候设置好程序,远程操作家中的各种宠物智能产品进行投喂陪玩等等,让宠物在家也可以无需担心安危,有需要的铲屎官们可以来了解一下哦! https://www.itmop.com/downinfo/508716.html
13.宠老板app下载宠老板(宠物店管理软件)v3.72.3安卓版下载宠老板是一款专用的宠物店管理软件,它可以在电脑、手机、平板上等多个端口安装,满足宠物店的日常收银,登记会员,寄样宠物等多个场景下进行使用,把宠物店铺安装上手机上,可以随时随地的记每日的流水,盘点店里面的库存,有效的管理店内商品。 应用功能 快速收银 ...https://www.jb51.net/softs/916889.html
14.免费03岁早教app排行榜0幼儿早教一直是现在宝爸宝妈们比较关心的事情,0-3岁幼儿早教是非常重要的,可以帮助孩子桁架有兴趣的去学习,E线软件园小编就为用户提供0-3岁早教app排行榜,让自己的孩子从小就具备学习的能力。https://www.edowning.net/top/szjappphb/
15.皇冠管理登录地址v7.38.26(中国)官方网站·IOS/手机版APP下载/APP点此查看更多好玩游戏排行榜 皇冠管理登录地址相关信息 时间 2024-10-30 分类 娱乐休闲竞技 标签 官网竞技体育游戏下载注册 要求 Android 5.7 以上 下载了皇冠管理登录地址的朋友还下载了 枪墓GORE 全面坦克模拟器 使命召唤:先锋 贝辛斯托克 我的英雄学院:一人的正义 Roblox Everreach:伊甸园计划 喋血复仇 尘埃拉力赛2.0...http://mipask.9939.com/q/4630616.html