Ngrx: combine two selectors - ngrx

I've built this IStore:
export interface IStore {
user: IUser;
sources: ISourceRedux;
}
where IUser is:
export interface IUser {
id: string;
cname: string;
sname: string;
...
}
and ISourceRedux is:
export interface ISourceRedux {
entities: { [key: string]: ISource };
ids: Array<string>;
selectedIds: Array<string>;
editingSource: ISource;
defaultId: string;
}
So, I've created these selectors:
export const getSourcesState = (state: IStore) => state.sources;
export const getSelectedIds = (sourceRdx: ISourceRedux) => sourceRdx.selectedIds;
export const getSelectedSourceIds = createSelector(getSourcesState, fromSources.getSelectedIds);
So, up to now, in order to check if a user is logged I did that:
this.store$
.select(fromRoot.getUserState)
.filter(user => user.id != null && user.logged)
.do(user => this.store$.dispatch(...))
...
Now I'm strugling for getting user information and selectedSourceIds at the same time in order to check if:
a user is logged (this.store$.select(fromRoot.getUserState)
then get all selectedSourceIds (this.store.select(fromRoot.getSelectedSourceIds))
dispatch an action
How could I get this?

Would it make sense to add that code to a selector:
// Selector functions
const getProductFeatureState = createFeatureSelector<ProductState>('products');
const getUserFeatureState = createFeatureSelector<UserState>('users');
export const getCurrentProduct = createSelector(
getProductFeatureState,
getUserFeatureState,
getCurrentProductId,
(state, user, currentProductId) => {
if (currentProductId === 0) {
return {
id: 0,
productName: '',
productCode: 'New',
description: 'New product from user ' + user.currentUser,
starRating: 0
};
} else {
return currentProductId ? state.products.find(p => p.id === currentProductId) : null;
}
}
);
This code is in the product.reducer file. Here I define the feature selector both for the products and for the users.
I then build a getCurrentProduct selector using both the product and user feature.

This is my solution:
this.store$.combineLatest(
this.store$.select(fromRoot.getUserEntity),
this.store$.select(fromRoot.getSelectedSourceIds),
(store, user, selectedSourceIds) => ({user: user, selectedSourceIds: selectedSourceIds})
)
.filter((proj) => proj.user.id != null && proj.user.logged)
.do((proj) => this.store$.dispatch({type: 'DELETE_CARDS', payload: {username: proj.user.username, tokens: proj.selectedSourceIds}}))
.take(1)
.subscribe();
I hope it's useful.

I created a feature selector that combines two features to accomplish this.
The feature selector for the global module:
export interface ScaffoldPartialState extends GlobalPartialState {
readonly [SCAFFOLD_FEATURE_KEY]: State;
}
which I import to the Scaffold selectors and have ScaffoldPartialState extend it.
export interface ScaffoldPartialState extends GlobalPartialState {
readonly [SCAFFOLD_FEATURE_KEY]: State;
}
The createFeatureSelector only returns the state typed so that the returned type looks like it contains only the state for this feature.
The value is the complete application state, but the type makes it looks like it's only for one module.
By one type extending the other, the resulting type provides a property for both modules.
AppState
{
module1: { ... },
module2: { ... },
module3: { ... }
}
Module2PartialState
{
module2: { ... },
}
Module2PartialState extends Module3PartialState
{
module2: { ... },
module3: { ... }
}
This way the ScaffoldPartialState feature selector works for selectors of both modules.
Example:
export const getAuthorizedMenuItems = createSelector(
getScaffoldState,
GlobalSelectors.getUserPermissions,
getMenuItems,
(_globalState, userPermissions, menuItems) =>
userPermissions ? menuItems.filter(e => userPermissions.checkAllPermissions(e.permissions)) : []
);

Related

NGRX Select returning Store not a value

catalogueSelection in Store Image
I have the data I require in state (catalogueSelection: searchTextResult & categoryCheckboxResult) and need to pass the string 'SearchTextResult' into one component and the Array 'categoryCheckboxResult' into another.
When I try to retrieve the required values I am retrieving the whole store. I have looked at numerous websites and entries here but getting very confused now.
Model:
export class SearchTextResult {
searchTextResult: string;
}
export class CategoryCheckboxResult {
categoryCheckboxResult:Array<CategoryCheckboxResult>;
}
Actions:
import { Action } from '#ngrx/store';
import { SearchTextResult, CategoryCheckboxResult } from 'app/#core/services/products/products.model';
export enum UserCatalogueSelectionTypes {
AddSearchTextResult = '[SearchTextResult] AddResult',
AddCategoryCheckboxResult = '[CategoryCheckboxResult] AddResult',
GetSearchTextResult = '[SearchTextResult] GetResult',
}
export class AddSearchTextResult implements Action {
readonly type = UserCatalogueSelectionTypes.AddSearchTextResult;
constructor(public payload: SearchTextResult){
}
}
export class AddCategoryCheckboxResult implements Action {
readonly type = UserCatalogueSelectionTypes.AddCategoryCheckboxResult;
constructor(public payload: CategoryCheckboxResult){
}
}
export class GetSearchTextResult implements Action {
readonly type = UserCatalogueSelectionTypes.GetSearchTextResult;
}
export type UserCatalogueSelectionUnion =
| AddSearchTextResult
| AddCategoryCheckboxResult
| GetSearchTextResult
Reducers:
import { SearchTextResult, CategoryCheckboxResult} from "app/#core/services/products/products.model";
import { UserCatalogueSelectionTypes, UserCatalogueSelectionUnion} from "../actions/products.actions";
export interface UserCatalogueSelectionState {
searchTextResult: SearchTextResult | null;
categoryCheckboxResult: CategoryCheckboxResult | null;
}
export const initialState: UserCatalogueSelectionState = {
searchTextResult: null,
categoryCheckboxResult: null,
}
export function reducer(state:UserCatalogueSelectionState = initialState, action: UserCatalogueSelectionUnion ): UserCatalogueSelectionState{
switch (action.type) {
case UserCatalogueSelectionTypes.AddSearchTextResult:
return {
...state,
searchTextResult: action.payload,
};
case UserCatalogueSelectionTypes.AddCategoryCheckboxResult:
return {
...state,
categoryCheckboxResult: action.payload,
};
case UserCatalogueSelectionTypes.GetSearchTextResult: {
return state;
}
default: {
return state;
}
}
}
Selectors:
import { createSelector,createFeatureSelector } from "#ngrx/store";
import {UserCatalogueSelectionState} from '../../store/reducer/products.reducer';
export const fetchSearchTextResults = createFeatureSelector<UserCatalogueSelectionState>("searchTextResult");
export const fetchSearchTextResult = createSelector (
fetchSearchTextResults,
(state:UserCatalogueSelectionState) => state.searchTextResult.searchTextResult
);
export const fetchCatalogueCheckBoxResults = createFeatureSelector<UserCatalogueSelectionState>("catalogueCheckboxResult");
export const fetchCatalogueCheckBoxResult = createSelector (
fetchCatalogueCheckBoxResults,
(state: UserCatalogueSelectionState) => state.categoryCheckboxResult.categoryCheckboxResult
);
My Component 1
Observable:
public searchTextResult: Observable<String>;
Contructor: (part of)
private store: Store<fromCatalogueSelection.UserCatalogueSelectionState>
Code Snippet: (asking for the data)
this.searchTextResult = this.store.select('SearchTextResult');
console.log('TESTING SEARCH TEXT: ', this.searchTextResult);
Console:
TESTING SEARCH TEXT: StoreĀ {_isScalar: false, actionsObserver: ActionsSubject, reducerManager: >ReducerManager, source: Store, operator: DistinctUntilChangedOperator}
My Component 2
Observable
searchTextResult$: Observable<CatalogueSelectionActions.GetSearchTextResult>;
Code Snippet: (asking for the data)
this.searchTextResult$ = this.store.select('GetSearchTextResult');
console.log('TESTING SEARCH TEXT: ', this.searchTextResult$);
Console:
TESTING SEARCH TEXT: StoreĀ {_isScalar: false, actionsObserver: ActionsSubject, reducerManager: > ReducerManager, source: Store, operator: DistinctUntilChangedOperator}
I've given up on the Selectors for the moment. Any help much appreciated as I'm going a round in circles.
You are almost there, the value from the console log is the observable object, everytime you select something from the store, you will get the value wrapped within an observable. You just need to subscribe to it:
this.searchTextResult$ = this.store.select('GetSearchTextResult');
this.searchTextResult$.subscribe((yourData) => console.log(yourData));
Also, since you are working with selectors, use them, you don't have to write the state/selector name:
selector
...
export const fetchCatalogueCheckBoxResult = createSelector (
fetchCatalogueCheckBoxResults,
(state: UserCatalogueSelectionState) =>
state.categoryCheckboxResult.categoryCheckboxResult
);
component
import * as YourSelectors from './store/something/selectors/yourthing.selectors'
...
...
this.searchTextResult$ = this.store
.select(YourSelectors.fetchCatalogueCheckBoxResult)
.subscribe(console.log);
Additionally, try to subscribe using the async pipe delegating that to your template html so you don't have to deal with the subscription in the code, for example:
component
...
export class Component {
searchTextResult$!: Observable<any> // your data type here
...
...
this.searchTextResult$ = this.store
.select(YourSelectors.fetchCatalogueCheckBoxResult)
}
html
<ng-container *ngIf="(searchTextResult$ | async) as result">
<p>Your result value: {{ result }}</p>
</ng-container>

How to extend EntityCollectionReducerMethods NgRx Data

I need to parse the data field before adding it to the store.
I was hoping to parse the data field from the override getAll().
This code doesn't work can someone explain why ?
export interface Alert {
id: string;
data: any;
}
const entityMetadata: EntityMetadataMap = {
Alert: {}
};
#Injectable({providedIn: 'root'})
export class AlertService extends EntityCollectionServiceBase<Alert> {
constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
super('Alert', serviceElementsFactory);
}
getAll(options?: EntityActionOptions): Observable<Alert[]> {
return super.getAll(options)
.pipe(
map(alerts => {
alerts = alerts.map((alert: any) => ({...alert, data: JSON.parse(alert.data)}));
return alerts;
})
);
}

How to get some value from Reducer Ngrx

I have the following reducer
import { EntityState, createEntityAdapter } from '#ngrx/entity';
import { createFeatureSelector } from '#ngrx/store';
export const pageAdapter = createEntityAdapter<Page>({
//selectId: (collection: Collection) => collection.id,
});
export interface State extends EntityState<Page> {
}
const defaultPage = {
ids: ['kHnryus'],
entities: {
'kHnryus': {
id: '83nryus',
title: 'How to create a blog with Angular4 Ngrx',
description: 'How to create a blog with Angular4 Ngrx',
}
},
success_create: false
}
export const initialState: State = pageAdapter.getInitialState();
// Reducer
export function pageReducer(
state: State = initialState,
action: actions.PageActions) {
switch (action.type) {
case actions.ADD_ALL: {
return pageAdapter.addAll(action.pages, state);
};
case actions.SUCCESS: {
return {success_create: true}
};
default:
return state;
}
}
// Create the default selectors
export const getPageState = createFeatureSelector<State>('page');
export const {
selectIds,
selectEntities,
selectAll,
selectTotal,
} = pageAdapter.getSelectors(getPageState);
I want to get the boolean variable success_create of the state in my component.
Basically , I want that if there is SUCCESS, I should be able to get a success_create true in the component class. I have no idea as to how to do this and even if it's possible.
If it is, please how can I achieve this?
First make sure to remove defaultPage constant as you are not maintaining it within your reducers and you are already using #ngrx/entity for that. The success_create should be defined as follow:
export interface State extends EntityState<Page> {
success_create: boolean;
}
export const adapter: EntityAdapter<Item> = createEntityAdapter<Page>({
selectId: (page: Page) => page.id,
sortComparer: false
});
export const initialState: State = adapter.getInitialState({
success_create: false,
});
Then, after your default selectors add a third one that uses getPageState selector and gets one step deeper into your state:
export const getSuccessCreate = createSelector(
getPageState,
(state: State) => state.success_create
);
Then make your component/service listening to it directly like:
this.created$ = this.store.pipe(select(fromPages.getSuccessCreate));
note: the pipe is optional here and if used then select should be imported from #ngrx/store as it is done in the official ngrx demo app. pipe allow you to work with lettable rxjs operators. you can read more about it here.

Get list as array from Firebase with angularfire2(v5) and ngrx effects

I have to tell you I'm getting crazy with it. I'm trying to get data from Firebase with AngularFire2(v.5) then work with it on #ngrx/effects and store it on #ngrx/store. Well, as I need the data with the keys, my code of effects looks like this:
spaces.effects.ts
#Effect()
getSpaces$ = this.actions$.ofType(SpacesActions.GET_SPACES_REQUEST)
.switchMap((action: SpacesActions.GetSpacesRequest) => {
return this.afs.list<Space>('/spaces').snapshotChanges()
.switchMap(actions => {
console.log('action is ', actions);
return actions.map(space => {
const $key = space.payload.key;
const data: Space = { $key, ...space.payload.val() };
console.log('snapshot is: ', data);
return new SpacesActions.GetSpacesSuccess(data);
});
}
);
My "actions" comes with the data and the key, then I get the key for each item because then I could update and delete items easily. My database has 3 items with 3 keys. If I run this code and log it, first I can see all items in 1 array with their payloads and with the second log I see each payload as snapshot.
When I call GetSpacesSuccess, I'd like to send all snapshots I got (with key and item) then store it. The way I'm doing now dispatch this action 3 times and I can see only 2 items on the screen because the first one is overridden by the second one.
So, two questions: Is there any easier way to get the items from firebase with their keys then store them with #ngrx? If not, what am I doing wrong that my first item is being overridden and my action is being dispatched 3 times?
Please, I'm doing my best with it as I'm learning. Thank you!
spaces.reducers.ts
case SpacesActions.GET_SPACES_REQUEST:
return {
state,
spaces: null,
loading: true
};
case SpacesActions.GET_SPACES_SUCCESS:
return {
...state,
...action.payload,
spaces: [state, action.payload],
loading: false
};
spaces.actions.ts
export class GetSpacesRequest implements Action {
readonly type = GET_SPACES_REQUEST;
}
export class GetSpacesSuccess implements Action {
readonly type = GET_SPACES_SUCCESS;
constructor(public payload: Space) {} <<<<<HERE I'D LIKE TO GET THE FULL ARRAY WITH EACH KEY
}
Thanks #AndreaM16 for the most complete answer. I went through the night working on it and I ended up doing it different. Actually, in the learning process we make mistakes in order to get the knowledge. Probably your solution is better than mine and I'll study that, thanks. Please, if possible, I'd love to hear your comments about my solution.
Finally, after reading lots of documentation, my effects is now this one, I don't have any error catcher though:
private spacesList = 'spaces/';
#Effect()
getSpaces$ = this.actions$.ofType(SpacesActions.GET_SPACES_REQUEST)
.switchMap(payload => this.afs.list(this.spacesList).snapshotChanges()
.map(spaces => {
return spaces.map(
res => {
const $key = res.payload.key;
const space: Space = {$key, ...res.payload.val()};
return space;
}
);
})
.map(res =>
new SpacesActions.GetSpacesSuccess(res)
));
Reducer
case SpacesActions.GET_SPACES_REQUEST:
return Object.assign({}, state, {
spaces: null,
loading: true
});
case SpacesActions.GET_SPACES_SUCCESS:
return Object.assign({}, state, {
spaces: action.payload,
loading: false
});
Actions
export class GetSpacesRequest implements Action {
readonly type = GET_SPACES_REQUEST;
}
export class GetSpacesSuccess implements Action {
readonly type = GET_SPACES_SUCCESS;
constructor(public payload: Space[]) {}
}
And, in my component, where I need the list:
constructor(private store: Store<fromSpaces.FeatureState>) {}
ngOnInit() {
this.store.dispatch(new SpacesActions.GetSpacesRequest());
this.spacesState = this.store.select('spaces');
}
If I understood your question correctly, you would like to store for each Item also store its key. You are looking for Map.
I would structure your feature as follows.
spaces.actions.ts:
Loading spaces requires no payload, while success has only an array of Space. I think you should build your Map<string,Space> in your reducer (string is your key).
import { Action } from '#ngrx/store';
/** App Models **/
import { Space } from './space.model';
export const GET_SPACES = '[Spaces] Spaces get';
export const GET_SPACES_SUCCESS = '[Start] Spaces get - Success';
export class GetSpacesAction implements Action {
readonly type = GET_SPACES;
}
export class GetSpacesActionSuccess implements Action {
readonly type = GET_SPACES_SUCCESS;
constructor(public payload: Space[]) {}
}
export type All
= GetSpacesAction
| GetSpacesActionSuccess;
spaces.effects.ts:
I'm assuming you just need a method to fetch spaces. If you need to do other stuff, just edit this piece of code. spaceService.getSpaces() is supposed to return only an array of Spaces. So, create a new Space model and, on your service, map each json entry to a new Space().
import { Injectable } from '#angular/core';
import { Actions, Effect } from '#ngrx/effects';
/** rxjs **/
import {map} from 'rxjs/operators/map';
import {mergeMap} from 'rxjs/operators/mergeMap';
import {catchError} from 'rxjs/operators/catchError';
/** ngrx **/
import * as spacesActions from './spaces.actions';
/** App Services **/
import { SpacesService } from './spaces.service';
#Injectable()
export class SpacesEffects {
#Effect() getSpaces$ = this.actions$
.ofType(spaceActions.GET_SPACES)
.pipe(
mergeMap(() => {
return this.spaceService.getSpaces()
.pipe(
map((spaces) => {
return new spacesActions.GetSpacesActionSuccess(spaces);
}),
catchError((error: Error) => {
// Handle erro here
})
);
})
)
;
constructor(private spacesService: SpacesService, private actions$: Actions) { }
}
spaces.reducer.ts
Here you build your map and you can also create a new action to return, for instance, a space given its key. I dont think you need any loading parameter here, I guess you are using it for some loading handling in your views, just use AsyncPipe in your view and handle a loading animation with an *ngIf checking if there are spaces or not.
/** ngrx **/
import {createFeatureSelector} from '#ngrx/store';
import {createSelector} from '#ngrx/store';
import * as spacesActions from './spaces.actions';
export type Action = spacesActions.All;
/** App Models **/
import { Space } from './space.model';
export interface SpaceState {
keySpaces: Map<string, Space>;
spaces: Space[];
keys: string[];
}
export const initialState: SpaceState = {
keySpaces: new Map<string, Space>(),
spaces: [],
keys: []
};
// Selectors
export const selectSpace = createFeatureSelector<SpaceState>('space');
export const getKeySpaces = createSelector(selectSpace, (state: StartState) => {
return state.keySpaces;
});
export const getSpaces = createSelector(selectSpace, (state: SpaceState) => {
return state.spaces;
});
export const getKeys = createSelector(selectSpace, (state: SpaceState) => {
return state.keys;
});
export function spacesReducer(state: SpaceState = initialState, action: Action): SpaceState {
switch (action.type) {
case startActions.GET_SPACES_SUCCESS:
// Since we return this from effect
const fetchedSpaces = action.payload;
const fetchedKeys = [];
const keySpacesMap = new Map<string, Space>();
fetchedSpaces.forEach( (space: Space) => {
fetchedkeys = fetchedKeys.concat(space.key);
keySpacesMap.set(space.key, new Space(space));
}
returns {
...state,
keySpaces: keySpacesMap,
spaces: fetchedSpaces,
keys: fetchedkeys
}
default: {
return state;
}
}
}
And, finally, you should be able to get such parameters in your components like:
. . .
keySpaces$ = Observable<Map<string, Space>>;
spaces$ = Observable<Array<Space>>;
keys$ = Observable<Array<string>>;
constructor(private _store: Store<AppState>) {
this.keySpaces$ = this._store.select(getKeySpaces);
this.space$s = this._store.select(getSpaces);
this.keys$ = this._store.select(getKeys);
}
. . .
ngOnInit() {
this._store.dispatch(new spacesActions.GetSpacesAction);
}
. . .
Of course add the new state to AppState:
. . .
export interface AppState {
. . .
space: SpaceState;
}

Redux state and component property undefined until ajax resolves

My component get some properties via props with the function:
const mapStateToProps = state => {
const { entities: { keywords } } = state
const {locale} = state
return {
keywords: keywords[locale]
}
}
I got state keywords using ajax, in the same component:
componentDidMount() {
this.props.loadKeywords()
}
My component gets rendered twice. First, before the ajax resolves, so in my render method I got undefined:
render() {
const { keywords } = this.props.keywords
...
Which is the proper way to solve it? I changed componentDidMount to componentWillMount without success.
Right now, based on the real-world example, I have initialized keywords state with an empty object:
function entities(state = { users: {}, repos: {}, keywords: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
My reducer:
import { combineReducers } from 'redux'
import { routerReducer as router } from 'react-router-redux'
import merge from 'lodash/merge'
import locale from './modules/locale'
import errorMessage from './modules/error'
import searchText from './modules/searchText'
// Updates an entity cache in response to any action with response.entities.
function entities(state = { users: {}, repos: {}, keywords: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
export default combineReducers({
locale,
router,
searchText,
errorMessage,
entities
})
My action:
import { CALL_API, Schemas } from '../middleware/api'
import isEmpty from 'lodash/isEmpty'
export const KEYWORDS_REQUEST = 'KEYWORDS_REQUEST'
export const KEYWORDS_SUCCESS = 'KEYWORDS_SUCCESS'
export const KEYWORDS_FAILURE = 'KEYWORDS_FAILURE'
// Fetches all keywords for pictos
// Relies on the custom API middleware defined in ../middleware/api.js.
function fetchKeywords() {
return {
[CALL_API]: {
types: [ KEYWORDS_REQUEST, KEYWORDS_SUCCESS, KEYWORDS_FAILURE ],
endpoint: 'users/56deee9a85cd6a05c58af61a',
schema: Schemas.KEYWORDS
}
}
}
// Fetches all keywords for pictograms from our API unless it is cached.
// Relies on Redux Thunk middleware.
export function loadKeywords() {
return (dispatch, getState) => {
const keywords = getState().entities.keywords
if (!isEmpty(keywords)) {
return null
}
return dispatch(fetchKeywords())
}
}
All based on the Real world redux example
My Solution
Given initial state to keywords entity. I'm getting json like this through ajax:
{'locale': 'en', 'keywords': ['keyword1', 'keyword2']}
However as I use normalizr with locale as id, for caching results, my initial state is as I describe in the reducer:
function entities(state = { users: {}, repos: {}, keywords: { 'en': { 'keywords': [] } } }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
What I don't like is the initial if we have several languages, also remembering to modify it if we add another language, for example fr. In this
keywords: { 'en': { 'keywords': [] } }
should be:
keywords: { 'en': { 'keywords': [] }, 'fr': { 'keywords': [] } }
This line looks problematic:
const { keywords } = this.props.keywords
It's the equivalent of:
var keywords = this.props.keywords.keywords;
I doubt that's what you intended.
Another thing worth checking is keywords[locale] in your mapStateToProps() which will probably initially resolve to undefined. Make sure your component can handle that, or give it a sensible default.

Resources