【问题标题】:Listen to ngrx Actions from Component收听来自组件的 ngrx 操作
【发布时间】:2020-04-07 11:58:28
【问题描述】:

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

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

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

        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});

        });

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

【问题讨论】:

    标签: angular ngrx


    【解决方案1】:

    更新

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

    可能的解决方案

    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();
    ...
    

    【讨论】:

    • 是的,这就是我目前正在做的事情,但是直接从组件中收听是一种好习惯吗?
    • 这是任何人都应该做的最新事情 :) 正确的方法是使用 ngrx/effects 并使用加载标志、异步任务进度等更新状态。
    • ?,是的,我对所有全局通知都这样做,但我无法真正从效果中访问组件元素
    • 对,完全理解你,因为它看起来有点不知所措。流程应该是下一个 - 你有一个滚动标志的状态,在 ngOnInit 中设置为 false 或任何负数。然后你有一个从标志单元的商店中选择它是积极的(例如id.id。在它的订阅中你调用scrollToElement。相关的效果应该调度一个将标志设置为id的动作。然后你可以避免直接监听 store 操作。
    • 不太了解,能否详细说明一下流程,谢谢
    猜你喜欢
    • 2021-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-06
    • 1970-01-01
    • 2017-07-08
    • 2017-04-03
    相关资源
    最近更新 更多