使用NgRx将对象数据集成到组件中,本地复制

Using NgRx to integrate object data into component, with local duplicate

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

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

我一直 运行 遇到问题让使用此数据的组件与服务和使用它的其他组件保持同步,但是,所以我决定尝试 NgRx,了解这个可观察的 -基于模式对于 Angular 来说更为典型,并且之前在许多 React 项目中使用过 Redux。

我 运行 在设置它时遇到了重大问题,但有两个原因:

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

  2. 更重要的是,我不知道如何在模板中显示过滤器值(如果我无法将其数据复制到本地对象,则尤其必要)。基本 getting started docs on NgRx 展示了如何使用 async 管道将数值合并到模板中,但是因为我的数据是对象形式,并且我想传递该对象的值,所以该技术不会工作。我已经根据上述 link 尝试了以下尝试 - 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) });

不确定是否可以通过过滤器的 keys/values 循环。

我在看完 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(模板对象值问题),但它确实没有实际意义,因为我能够轻松地复制商店内容在本地,并在提交时更新它们。