使用NgRx将对象数据集成到组件中,并具有本地副本

时间:2018-12-07 00:23:32

标签: angular ngrx ngrx-store ngrx-store-4.0

在Angular应用中,我有一个页面,其中包含一组可以切换然后立即提交的过滤器。

我使用基本服务构建了此服务,该服务具有过滤器对象(指向过滤器值的过滤器名称)。此服务的数据结构被复制到组件(localFilters)中的本地版本中,该版本会随着用户单击复选框等而更新。如果用户单击按钮提交过滤器,则会设置本地过滤器到全局过滤器服务,并且如果用户未提交就退出,则它不会更新全局服务(并且localFilters在退出时被清除)。

我一直遇到让使用此数据的组件与服务以及使用它的其他组件保持同步的问题,因此,我决定尝试NgRx,因为了解这种基于可观察的模式对于Angular之前曾在多个React项目中使用过Redux。

但是,由于两个原因,我在设置它时遇到了重大问题:

  1. 我之前在服务中使用的模式涉及根据全局过滤器对象的状态在组件安装上设置localFilters对象。这些localFilters将用于确定页面上过滤器的开始状态,并在提交时进行全局设置。但是,使用NgRx使用的可观察模式,没有要复制的filters对象,只有一个可观察对象,因此我不知道如何初始化localFilters对象。结果,我不知道如何从该全局对象设置各种过滤器组件的默认状态。

  2. 更基本而言,我不知道如何在模板中显示过滤器值(尤其是在无法将其数据复制到本地对象的情况下尤其如此)。基本的getting started docs on NgRx显示了如何使用async管道将数字值合并到模板中,但是由于我的数据是对象形式的,并且我想传递该对象的值,因此该技术不会工作。我已经根据上述链接尝试了以下尝试-filters$ | async(显示[Object object]),filters$.someKey | async(不显示任何内容)和(filters$ | async).someKey(类似地不显示任何内容)。

基本上,最大的问题是我如何访问以NgRx状态存储的对象的快照,以初始化此滤镜组件的本地状态,以及如何从对象中渲染值(或传递这些值)。模板。

还是我应该遵循的更好的模式? (很难找到好的例子,将不胜感激。)

下面是一堆我的相关代码。

动作文件:

import { Action } from '@ngrx/store';

export enum ActionTypes {
  SetFilter = 'SetFilter',
  SetFilters = 'SetFilters',
  ClearFilters = 'ClearFilters',
}

export class SetFilter implements Action {
  readonly type = ActionTypes.SetFilter;
  constructor(public name: string, public value: any) {}
}

export class SetFilters implements Action {
  readonly type = ActionTypes.SetFilters;
  constructor(public filters: object) {}
}

export class ClearFilters implements Action {
  readonly type = ActionTypes.ClearFilters;
}

export type ActionsUnion = SetFilter | SetFilters | ClearFilters;

Reducers文件:

import * as FilterActions from './actions';

export interface State {
  filters: object
};

export const initialState: State = {
  filters: { wassup: 'true' } // testing initial state with some nonsense
};

export function reducer(state = initialState, action: FilterActions.ActionsUnion) {
  switch (action.type) {
    case FilterActions.ActionTypes.SetFilter: {
      return { ...state, [action.name]: action.value };
    }
    case FilterActions.ActionTypes.SetFilters: {
      return { ...state, ...action.filters };
    }
    case FilterActions.ActionTypes.ClearFilters: {
      return {};
    }
    default: return state;
  }
}

AppModule的缩写:

import { StoreModule } from '@ngrx/store';
import { reducer } from './ngrx/filters/reducer';

@NgModule({
  declarations: [...],
  imports: [
    ...,
    StoreModule.forRoot({ filters: reducer })
  ],
  ...
})

以及相关component.ts文件的缩写版本:

@Component({
  selector: 'app-base-filter',
  templateUrl: './base-filter.component.html',
  styleUrls: ['./base-filter.component.scss']
})
export class BaseFilterComponent implements OnInit {
  /** Object with selected indices for given filter keys. */
  selectedIndices: any = {};

  /** Duplicate all filters locally, to save on submit and clear on cancel */
  localFilters: any = {};

  filters$: Observable<object>;

  constructor(private store: Store<{ filters: object }>) {
    this.filters$ = store.pipe(select('filters'));

    this.initLocalFilters();
  }

  ngOnInit() {}

  // This worked with the old filtersService model
  // But is obviously broken here, because I haven't been able to init
  // localFilters correctly.
  initLocalFilters () {
    this.localFilters = {};

    // Fill pre-selections from filter service
    ['this', 'is a list of', 'names of filters with', 'an array of options']
      .forEach((arrayKey) => {
        // The selected indices are used in the template to pass to child 
        // components and determine selected content.
        this.selectedIndices[arrayKey] = (this.localFilters[arrayKey] || [])
          .map(t => this[arrayKey].indexOf(t));
      });
  }
});

顺便说一下,我已经在上面的组件构造函数中尝试了以下一些方法:

// Doesn't throw an error, but doesn't enter the callback
this.store.select(data => { console.log(data) });

// Doesn't throw an error, but filter is undefined inside the loop
this.filters$ = store.pipe(select('filters'));
this.filters$.forEach(filter => { console.log(filter) });

不确定是否可以遍历过滤器的键/值。

1 个答案:

答案 0 :(得分:1)

观看this slightly outdated but useful example video后,我找到了答案(对于发现文档非常缺乏的其他人)。没什么太疯狂的。我只是不完全了解RxJs是如何集成的。

我的组件代码只需要更改:

import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs';

// These are local files. The @ format is just part
// of some path aliasing I've set up.
import { SetFilters } from '@store/filters/actions';
import { AppState } from '@store/reducers'; // Reducer interface

@Component({
  selector: 'app-base-filter',
  templateUrl: './base-filter.component.html',
  styleUrls: ['./base-filter.component.scss']
})
export class BaseFilterComponent implements OnInit {
  /** Object with selected indices for given. */
  selectedIndices: any = {};

  /** Duplicate all filters locally, to save on submit and clear on cancel */
  localFilters: any = {};

  /** Filters reducer */
  filters$: Observable<object>;

  constructor(private store: Store<AppState>) {
    this.filters$ = this.store.pipe(select('filters'));
    this.initLocalFilters();
  }

  ngOnInit() {}

  /**
   On component mount, clear any preexisting filters (if not dismounted),
   subscribe to filters in store, and initialize selectedIndices from filters.
  */
  initLocalFilters () {
    this.localFilters = {};

    this.filters$.subscribe(filters => {
      this.localFilters = { ...filters };
    });

    // Fill pre-selections from filter service
    ['this', 'is a list of', 'names of filters with', 'an array of options']
      .forEach((arrayKey) => {
        this.selectedIndices[arrayKey] = (this.localFilters[arrayKey] || [])
          .map(t => this[arrayKey].indexOf(t));
      });
  }

  ...

  submitFilters() {
    this.store.dispatch(new SetFilters(this.localFilters));
  }
}

显然,这不能直接解决问题 2 (模板对象值问题),但确实可以解决问题,因为我能够轻松地在本地复制商店内容并更新它们在提交时。