ngrx 入门

什么是ngrx

ngrx是Angular基于Rxjs的状态管理,保存了Redux的核心概念,并使用RxJs扩展的Redux实现。使用Observable来简化监听事件和订阅等操作。
在看这篇文章之前,已经假设你已了解rxjsredux
有条件的话请查看官方文档进行学习理解。

所需包

ng add方式,会自动生成所需文件并在app.module.ts中导入,直接启动项目即可,不报错

ng add @ngrx/store
ng add @ngrx/store-devtools
ng add @ngrx/effects
ng add @ngrx/router-store
为了不浪费时间,直接给你们demo代码,提取码wkkd

本教程使用的代码。建议使用此代码学习,方便上手,考虑到有同学们打不开github,特意传到百度云做分享的……

Angular的流程

  • compone.ts定义变量a
  • 改变a
  • (没有数据请求的) this.a = '';
  • (有数据请求的) this.a = this.services.search()类似的写法

用ngrx后的Angular的基本流程

  • 写action.ts:为要改变数据的操作定义不同action
  • 写effects.ts(有数据请求的):通过判断action.type,来发起service
  • 写reducer.ts:判断action.type,返回新的store数据
  • component.ts:从store取数据
    最后,告诉应用程序,你写了这玩意
    处理流程
项目简介

本项目的两个demo,都挂在example模块中

计数器-没有数据请求 app/example/container/counter.component.ts
1、先写action(减和重置的action在代码里面,可自行查看)
// app/example/actions/counter.actions.ts
import { Action } from '@ngrx/store';

export const INCREMENT = '[Counter] Increment';  

export class IncrementAction implements Action {
  readonly type = INCREMENT;
  constructor() { }
}

export type Actions
  = IncrementAction;
2、写reducer(由于加不需要发送网络请求,就不写effect)
// app/example/reducer/counter.reducer.ts
import * as counter from '../actions/counter.actions';
// 定义接口,后续counter页面如果有其他数据要保存,可以在这个State里面继续添加,不要忘记在initialState也要加
export interface State {
  counter: number;
}
// 定义初始化数据
const initialState: State = {
  counter: 0
};
// 定义reducer 根据action type来处理状态,改变对应数据
export function reducer(state = initialState, action: counter.Actions): State {
  switch (action.type) {
    case counter.INCREMENT: 
      return {
        counter: state.counter + 1
      };

    default:
      return { ...state };
  }
}

export const getCounter = (state: State) => state.counter;

// app/example/reducer/index.ts
import {createFeatureSelector, createSelector} from '@ngrx/store';

// 第二步 导入我们的reducer文件
import * as fromCounter from './counter.reducer';

export interface State {
  counter: fromCounter.State;
}
export const reducers = {
  counter: fromCounter.reducer
};

//  将模块example的所有数据挂在store上,命名为example,里面保存example模块里面所有页面的数据
export const getExampleState = createFeatureSelector<State>('example');

// 计数器页面
export const getCounterState = createSelector(getExampleState, (state: State) => state.counter);
export const getCounterCounter = createSelector(getCounterState, fromCounter.getCounter);
3、在模块中导入reduecer,这样就可以运行
// app/example/example.module.ts

import {StoreModule} from '@ngrx/store';
import {reducers} from './reducer';
@NgModule({
  imports: [
    StoreModule.forFeature('example', reducers) // 这个才是关键哦
  ]
})
export class ExampleModule { }

4、到这一步了,我们的页面就来操作并且取数据,增加啦
// src/example/container/counter.component.ts

import * as fromExample from '../../reducer';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import * as counterAction from '../../actions/counter.actions';

export class CounterComponent {
  counter$: Observable<number>;
  constructor(private store: Store<fromExample.State>) {
    this.counter$ = store.select(fromExample.getCounterCounter);
  }

  increment() {
    this.store.dispatch(new counterAction.IncrementAction());
  }
}

页面

// app/example/container/counter.component.html
<div class="container">
  <p class="title">计数器 没有数据请求的ngrx demo</p>

  <div class="btn-group">
    <button nz-button nzType="primary" (click)="increment()">加</button>
    <span style="font-size: 18px;font-weight: bold">当前数:{{counter$ | async}}</span>
  </div>
</div>

npm run start看一下页面效果吧

搜索图书-有数据请求的 src/app/example/containers/book-manage/book-manage.component.ts
1、写action (src/app/example/actions/book-manage.actions.ts)看注释
import { Action } from '@ngrx/store';
import {Book} from '../model/book';
// 搜索,然后搜索成功,两件事情,分两个action
export const SEARCH =           '[Book Manage] Search';
export const SEARCH_COMPLETE =  '[Book Manage] Search Complete';
// 搜索图书需要带书名称来搜索,所以SearchAction有一个字符串类型的参数payload
export class SearchAction implements Action {
  readonly type = SEARCH;
  constructor(public payload: string) { }
}
// 搜索出结果后,图书的信息我们将其定义类型为Book,结果放在数组里面,所以SearchCompleteAction 的参数是Book类型数组
export class SearchCompleteAction implements Action {
  readonly type = SEARCH_COMPLETE;
  constructor(public payload: Book[]) { }
}
export type Actions
  = SearchAction
  | SearchCompleteAction;
2、写reducer(src/app/example/reducer/book-manage.reducer.ts)
import * as bookManage from '../actions/book-manage.actions';
import {Book} from '../model/book';
// book-manage页面我们暂时定义一个list变量,用了存搜索图书的结果,后续如果有其他变量,继续在里面加
export interface State {
  list: Book[];
}
// 默认一开始搜索数据为空数组[]
const initialState: State = {
  list: []
};
// 当搜索成功后,更新state里面的list值
export function reducer(state = initialState, action: bookManage.Actions): State {
  switch (action.type) {
    case bookManage.SEARCH_COMPLETE:
      return {
        ...state,
        list: action.payload
      };

    default:
      return { ...state };
  }
}

export const getList = (state: State) => state.list;

3、更新reducer (src/app/example/reducer/index.ts)
import {createFeatureSelector, createSelector} from '@ngrx/store';
import * as fromCounter from './counter.reducer';
// 导入写好的book-manage.reducer.ts
import * as fromBookManage from './book-manage.reducer';  
export interface State {
  counter: fromCounter.State;
  bookManage: fromBookManage.State;
}
export const reducers = {
  counter: fromCounter.reducer,
  bookManage: fromBookManage.reducer
};
export const getExampleState = createFeatureSelector<State>('example');
// 计数器
export const getCounterState = createSelector(getExampleState, (state: State) => state.counter);
export const getCounterCounter = createSelector(getCounterState, fromCounter.getCounter);
// 图书管理
export const getBookManageState = createSelector(getExampleState, (state: State) => state.bookManage);
export const getBookManageList = createSelector(getBookManageState, fromBookManage.getList);
4、写effect,检测action类型,如果是search图书行为,就发送网络请求查结果(src/app/example/effects/book-manage.effect.ts)
import {Injectable} from '@angular/core';
import {Actions, Effect, ofType} from '@ngrx/effects';
import {Observable, of} from 'rxjs';
import {Action} from '@ngrx/store';

import * as bookManage from '../actions/book-manage.actions';
import {BookManageService} from '../services/book-manage.service';
import {catchError, debounceTime, map, mergeMap, skip, takeUntil} from 'rxjs/operators';
import {BookResult} from '../model/book';

@Injectable()
export class BookManageEffects {
  @Effect()
  searchBook$: Observable<Action> = this.actions$.pipe(
    ofType(bookManage.SEARCH),    // 判断是不是搜索图书的行为
    debounceTime(300),   // 做延迟,节省网络请求
    mergeMap((action: bookManage.SearchAction) => {
      const nextSearch$ = this.actions$.pipe(ofType(bookManage.SEARCH), skip(1));
      return this.service.searchBooks(action.payload).pipe(
        takeUntil(nextSearch$),
        // 如果搜索成功,发送成功action并且带入搜索结果
        map((data: BookResult) => ({type: bookManage.SEARCH_COMPLETE, payload: data.items})),
        // 如果搜索失败404之类的原因,发送成功action,以及空数组更新结果
        catchError(() => of({type: bookManage.SEARCH_COMPLETE, payload: []}))
      );
    })
  );

  constructor(private actions$: Actions, private service: BookManageService) {
  }
}
5、component获取搜索结果和绑定搜索事件(src/app/example/containers/book-manage/book-manage.component.ts)
import { Component, OnInit } from '@angular/core';
import {Store} from '@ngrx/store';
import * as fromExample from '../../reducer';
import * as bookManageAction from '../../actions/book-manage.actions';
import {Observable} from 'rxjs';
import {Book} from '../../model/book';
@Component({
  selector: 'app-book-manage',
  templateUrl: './book-manage.component.html',
  styleUrls: ['./book-manage.component.css']
})
export class BookManageComponent implements OnInit {
  searchResult$: Observable<Book[]>;  // 搜索到的图书列表

  constructor(private store: Store<fromExample.State>) {
    // 从store中选取图书搜索结果
    this.searchResult$ = this.store.select(fromExample.getBookManageList);
  }

  ngOnInit() {
  }
  // 发送搜索行为
  search(bookName) {
    this.store.dispatch(new bookManageAction.SearchAction(bookName));
  }

}

页面(src/app/example/containers/book-manage/book-manage.component.html)

<div class="container">
  <app-search-input (search)="search($event)"></app-search-input>
  <app-book-list [bookList]="searchResult$ | async"></app-book-list>
</div>

6、导入effect,否则会不起作用哦 (src/app/example/example.module.ts)
...
import {BookManageEffects} from './effects/book-manage.effect';
import {EffectsModule} from '@ngrx/effects';

@NgModule({
   ...
  imports: [
    EffectsModule.forFeature([BookManageEffects]),
  ],

好了,启动项目,看一下效果啦!

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值