【问题标题】:@ngrx 4 how to filter current loaded data@ngrx 4 如何过滤当前加载的数据
【发布时间】:2017-11-09 21:15:30
【问题描述】:

我正在开发一个新的 angular 4 plus @ngrx 4 项目。

我希望对加载的数据有搜索功能。

例如,所有联系人信息都已加载到组件中。 联系人列表将筛选出与搜索文本匹配的联系人姓名。

Please see screenshot

由于数据已经存在存储中,我不希望再次调用 web api 服务。

任何想法或演示代码将不胜感激。

【问题讨论】:

    标签: ngrx


    【解决方案1】:

    您可以按照此流程在已获取的内容上搜索您需要的内容:

    在您的输入中使用类似'(input)'='searchInputChange$.next(search)' 的内容。因此,每次用户更改输入时,都会触发我们的研究。

    然后,在您的组件上,在构造函数上,每次searchInputChange$ 更改时,我们都会触发一个新的SearchAction。然后,我们将在 reducer 上更改过滤后的内容,并将结果插入到contents$。在ngOnInit 上,我们只是第一次从 api 加载数据。

    我正在使用一个名为Content 的模型,这只是一个示例,它有一个字符串参数title。我们将使用此字段根据搜索输入过滤我们的内容。

    import { Component, OnInit } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { Subject } from 'rxjs/Subject';
    import {of} from 'rxjs/observable/of';
    
    /** ngrx **/
    import {AppState} from '../../app-state.interface';
    import * as searchActions from './actions/search.actions';
    
    /** App Models **/
    import { Content } from './models/content.model';
    
    export class SearchComponent implements OnInit {
    
        searchInputChange$ = new Subject<string>();
        contents$: Observable<Array<Content>>;
    
        constructor(private _store: Store<AppState>) {
          this.searchInputChange$
            .switchMap((text: string) => of(text))
            .subscribe((text: string) => this._store.dispatch(new searchActions.SearchAction(text)));
          this.contents$ = this._store.select(getSearchedContents);
        }
    
        ngOnInit() {
            this._store.dispatch(new searchActions.LoadAction());
        }
    
    }
    

    然后,我们将拥有我们的SearchActionsLoad 在我们组件的初始化时触发,从 api 获取一些内容。 LoadSuccess 在加载操作的影响下发出,以便用获取的数据填充我们的 reducer 并将其显示在我们的第一个组件中,它有一个内容数组的有效负载。 Search 将在我们的输入字段更改时触发,这将有一个包含搜索字符串的字符串有效负载。

    import { Action } from '@ngrx/store';
    
    /** App Models **/
    import { Content } from '../models/content.model';
    
    export const LOAD = '[Search] Load';
    export const LOAD_SUCCESS = '[Search] Load Success';
    export const SEARCH = '[Search] Search';
    
    export class LoadAction implements Action {
      readonly type = LOAD;
      constructor() { }
    }
    
    export class LoadActionSuccess implements Action {
      readonly type = LOAD_SUCCESS;
      constructor(public payload: Content[]) { }
    }
    
    export class SearchAction implements Action {
      readonly type =  SEARCH;
      constructor(public payload: string) {}
    }
    
    export type All
      = LoadAction
      | LoadActionSuccess
      | SearchAction;
    

    SearchEffect 只会从 api 获取内容:

    import { Injectable } from '@angular/core';
    import { Actions, Effect } from '@ngrx/effects';
    
    /** rxjs **/
    import {of} from 'rxjs/observable/of';
    import {map} from 'rxjs/operators/map';
    import {mergeMap} from 'rxjs/operators/mergeMap';
    import {catchError} from 'rxjs/operators/catchError';
    
    /** ngrx **/
    import * as searchActions from '../actions/search.actions';
    
    /** App Services **/
    import { SomeService } from '../services/some.service';
    
    /** App Model **/
    import {Content} from '../models/content.model';
    
    @Injectable()
    export class SearchEffects {
    
      @Effect() load$ = this.actions$
        .ofType(searchActions.LOAD)
          .pipe(
            mergeMap(() => {
              return this.someService.getContentsFromApi()
                .pipe(
                  map((contents: Content[]) => {
                    return new searchActions.LoadActionSuccess(contents);
                  }),
                  catchError(() => {
                    // do something
                  })
                );
            })
        )
      ;
    
      constructor(private someService: SomeService, private actions$: Actions) { }
    }
    

    SearchReducer 将在我们成功从 api 获取内容时处理 LoadSuccessSearch 操作,该操作将过滤我们获取的内容以仅返回在内容的 title 参数中包含我们搜索字符串的内容。我们将第一次获取的内容保存在contentssearchedContents 中。然后,在搜索时,我们将更新searchedContents,使其仅包含contents,而content.title 包括搜索到的字符串。

    import { isEmpty } from 'lodash';
    
    /** ngrx **/
    import {createFeatureSelector} from '@ngrx/store';
    import {createSelector} from '@ngrx/store';
    
    /** App Models **/
    import { Content } from '../models/content.model';
    
    /** ngrx **/
    import * as searchActions from '../actions/search.actions';
    
    export type Action = searchActions.All;
    
    export interface SearchsState {
      contents: Content[];
      searchedContents: Content[];
    }
    
    export const initialState: SearchsState = {
      contents: [],
      searchedContents: []
    };
    
    / -------------------------------------------------------------------
    // Selectors
    // -------------------------------------------------------------------
    export const selectContents      = createFeatureSelector<SearchsState>('search');
    export const getSearchedContents = createSelector(selectContents, (state: searchedContents) => {
      return state.searchedContents;
    });
    
    export function contentsReducer(state: searchedContents = initialState, action: Action): searchedContents {
      switch (action.type) {
        case contentsActions.LOAD_SUCCESS:
          const loadContents = action.payload.map(content => new Content(content));
          return {
                   contents: loadContents,
                   searchedContents: loadContents
          };
        case contentsActions.SEARCH:
          const keywordContents = isEmpty(action.payload) ? state.contents :
              state.contents.filter(content => content.title.includes(action.payload));
          return {
                   contents : state.contents,
                   searchedContents : keywordContents
          };
        default: {
          return state;
        }
      }
    }
    

    所以,更新searchedContents 会自动更新我们组件中的内容。

    【讨论】:

    • 如果我们添加了其他操作,例如更新、删除、创建内容。我们是否需要对搜索的内容和内容都执行这些操作?例如,如果用户正在搜索并删除其中一个搜索的内容,我们需要从内容和搜索的内容中删除内容,对吧?
    【解决方案2】:

    ngrx 存储是您如何存储数据的一部分。 ngrx 存储是可观察的,因此您的应用程序流程是

    容器 -> 组件

    Container - 将从存储中选择数据的包装器组件。 示例:

    const contacts$: Observable<contact> = this.store.pluck('contacts');
    

    //*contacts$ - 因为美元是 Observable 的约定 *//

    组件 - 数据可视化组件,数据将作为 Input()。示例:

    Input() contacts: Array<contact>;
    

    这个约定有时被称为 SmartComponent(Container) 并且 哑组件(组件)

    现在对于数据转换/映射,您可以使用响应式方法 (Rxjs) 或函数式编程或任何您想要的,但它与 ngrx 无关,因为在您的联系人组件中存在数据。

    您的场景演示:

    contacts.container.ts

    @Component({
        selector: 'contacts-container',
        template: `
        <contacts-list [contacts]="contacts$ | async"></contacts-list>
        `
    })
    
    export class ContactsContainer {
        contacts$: Observable<[]contact> = this.store.pluck('contacts');
        constructor(
            private store: Store<applicationState>
        ) { }
    }
    

    contact-list.component.ts

    @Component({
        selector: 'contacts-list',
        template: `
            <input type="text" placeholder="write query" #query>
            <ul>
                <li *ngFor="contact of contacts | searchPipe: query.target.value">
                </li>
            </ul
        `
    })
    
    export class ContactsListComponent {
        contcats: Array<contact> = [];
        constructor() { }
    
    }
    

    我使用 searchPipe 进行数据转换(自定义管道),但这只是数据转换的示例,您可以做其他事情。

    祝你好运!

    【讨论】:

    • 很清楚。我更喜欢安德里亚的这个答案。将数据加载到组件中(如果您愿意,可以使用 NgRx)。然后在组件内用它做你需要的事情;比如过滤。使用管道非常适合使用 Observables 动态更新内容,因为来自 Observable 流的任何内容都会进入管道,您可以链接管道以非常轻松地适应您的需求,而无需进一步混淆 NgRx 概念或担心如何存储和刷新数据。如何实现自定义过滤器也很容易谷歌:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-07
    • 2017-04-18
    • 1970-01-01
    相关资源
    最近更新 更多