首页 > 解决方案 > 收听来自组件的 ngrx 操作

问题描述

我正在使用 ngrx/angular8 构建一个应用程序,在一种情况下,我想响应来自不同组件的操作。传统的方法是向 store 添加另一个属性并为其创建 reducer/selector。问题是我希望其他组件响应事件,即使它具有相同的值。例如让我们分解:

  1. 我单击了其中一个组件中的按钮,它调度了动作this.store.dispatch( LayoutActions.scrollToSession({id: session_id})
  2. 我希望 3 个不同的组件以某种方式响应此操作,即使它们session_id是相同的。因此,如果我在减速器中创建了一个属性,则选择器将仅在第一次获得更新,并且不会为后续操作触发。

我的解决方案是简单地调度动作,并在组件中监听动作:

        this.actions$.pipe(
            ofType(LayoutActions.scrollToSession),
            takeUntil(this.unsubscribe)
        ).subscribe(id => { 

            if ((!id.id) || (!this.messages_list)) return;
            this.messages_list.scrollToElement(`#session-${id.id}`, {left: null, top: null});

        });

我的问题是,这是正确的方法吗?直接监听组件中的操作,如果有的话,我有什么选择?我虽然在调度操作时添加了一个随机前缀,以更改存储状态并稍后在选择器中将其删除,但这感觉不对。

标签: angularngrx

解决方案


更新

正确的方法是始终依赖存储状态,而不是其操作。

可能的解决方案

store.ts

import {Injectable} from '@angular/core';
import {Actions, createEffect, ofType} from '@ngrx/effects';
import {Action, createAction, createFeatureSelector, createReducer, createSelector, on, props} from '@ngrx/store';
import {delay, map} from 'rxjs/operators';

// actions
export const setScroll = createAction('scroll', props<{id?: string, shaker?: number}>());
export const causeTask = createAction('task', props<{scrollId: string}>());

// reducer
export interface State {
    scroll?: {
        id: string,
        shaker: number,
    };
}

const reducer = createReducer(
    {},

    on(setScroll, (state, {id, shaker}) => ({
        ...state,
        scroll: id ? {id, shaker} : undefined,
    })),
);

export function coreReducer(state: State, action: Action): State {
    return reducer(state, action);
}

export const selectState = createFeatureSelector<State>('core');

export const selectFlag = createSelector(
    selectState,
    state => state.scroll,
);

// effects
@Injectable()
export class Effects  {
    public readonly effect$ = createEffect(() => this.actions$.pipe(
        ofType(causeTask),
        delay(5000),
        map(({scrollId}) => setScroll({id: scrollId, shaker: Math.random()})),
    ));

    constructor(protected readonly actions$: Actions) {}
}

app.component.ts

import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
import {Store} from '@ngrx/store';
import {filter, map} from 'rxjs/operators';
import {causeTask, selectFlag, setScroll} from 'src/app/store';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent implements OnInit {

    constructor(protected store: Store) {
    }

    public ngOnInit(): void {
        // reset of the scrolling state
        this.store.dispatch(setScroll({}));

        this.store.select(selectFlag).pipe(
            filter(f => !!f),
            map(f => f.id),
        ).subscribe(value => {
            this.store.dispatch(setScroll({})); // reset
            alert(value); // <- here you should use the scrolling.
        });

        // some long task which result should cause scrolling to id.id.
        this.store.dispatch(causeTask({scrollId: 'value of id.id'}));
        this.store.dispatch(causeTask({scrollId: 'value of id.id'}));
    }
}

app.module.ts

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {EffectsModule} from '@ngrx/effects';
import {StoreModule} from '@ngrx/store';
import {coreReducer, Effects} from 'src/app/store';

import {AppComponent} from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
      StoreModule.forRoot({
        core: coreReducer,
      }),
      EffectsModule.forRoot([
        Effects,
      ]),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

原来的

如果您需要这些操作,您可以使用他们的流。

import {StoreActions, StoreState} from '@core/store';


...
    constructor(
        protected readonly storeActions: StoreActions,
    ) {}
...

...
        // listening on success action of company change request.
        this.storeActions
            .ofType(CompanyProfileActions.UpdateBaseSuccess)
            .pipe(takeUntil(this.destroy$))
            .subscribe();
...

推荐阅读