I'm building an app using ngrx/angular8, and there is one scenario where i want to respond to an action from different components. The traditional way is to add another property to the store and create the reducer/selector for it. Problem is I want for the other components to respond to the event even if it has the same value. e.g. lets break down:
this.store.dispatch( LayoutActions.scrollToSession({id: session_id})
session_id
is the same. So If i created a property in the reducer, the selector will get the update only for the first time and won't fire for subsequent actions.My solution was simply to dispatch the action, and in the component im listening for the action:
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});
});
My question is, is this the right approach? listening for the actions in the components directly and what are my alternatives if any? I though of adding a random prefix when dispatch the action, to change the store state and remove it later in the selector, but that doesn't feel right.
UPDATED
The right way is to always rely on the store state, not on its actions.
possible solution
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 { }
ORIGINAL
If you need the actions you can use their stream.
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();
...