Building state with ngxs in angular
//Component.ts
ngOnInit(): void {
const dataInsdeStore = this.store.selectSnapshot(MarketState);
if(!dataInsdeStore.loaded){
const category = this.marketplaceService.getMarketplaceCategories().subscribe(cat => {
const item = this.marketplaceService.getMarketplaceItems().subscribe(i => {
const obj = {
categories: cat,
items: i,
loaded: true
}
this.store.dispatch(new SetMarketAction(obj))
})
})
}
const test = this.store.selectSnapshot(MarketState);
this.store.dispatch(new GetMarketStateAction())
}
Upon checking wether the state is empty or not, and dispatching SetMarketAction I get empty object anyways
//Market.actions.ts
import { MarketStateInterface } from "../interfaces/interfaces";
class SetMarketAction {
static readonly type = '[MARKET] Set';
constructor(public marketState : MarketStateInterface){}
}
class GetMarketStateAction {
static readonly type = '[MARKET] Get';
}
export { SetMarketAction, GetMarketStateAction };
//Market.state.ts
import { Action, State, StateContext } from '#ngxs/store';
import { MarketStateInterface } from '../interfaces/interfaces';
import { Injectable } from '#angular/core';
import { GetMarketStateAction, SetMarketAction } from './market.actions';
#State<MarketStateInterface>({
name: 'market',
defaults: {
categories: [],
items: [],
loaded: false,
},
})
#Injectable()
export class MarketState {
constructor() {}
#Action(GetMarketStateAction)
getMarket(ctx: StateContext<MarketStateInterface>) {
const state = ctx.getState();
console.log(state);
return state;
}
#Action(SetMarketAction)
setMarket(ctx: StateContext<MarketStateInterface>, action: SetMarketAction) {
ctx.setState(action.marketState);
}
}
Every time I conosle.log the state at any given time I get the empty array, the interface of the state is correct
Related
I am using mobx and decimal.js.
This is my store:
import Decimal from "decimal.js";
import { makeObservable, observable, action } from "mobx";
class MyStore {
public value: Decimal | null = null;
constructor() {
makeObservable(this, {
value: observable,
setValue: action,
});
}
public setValue() {
this.value = new Decimal(100);
}
}
export { MyStore };
This is my component:
import { useStoreValue } from "../../state/StoreContext";
import { observer } from "mobx-react-lite";
const MyPage = observer(() => {
const value = useStoreValue((rootStore) => rootStore.myStore.value);
return <span>{value.mul(5)}</span>;
});
export { MyPage };
As a result I get the following exception:
useObserver.ts:119 Uncaught TypeError: _value.mul is not a function
Any idea what I am missing?
I have a React Context file with a stateful Provider to manage cookie preferences. It all works as expected but I'm having 1 issue with Typescript. My file looks like this:
import Cookies from 'js-cookie';
import React, { Component, ReactNode } from 'react';
interface Props {
children: ReactNode | ReactNode[];
functionalCookiesOn: boolean;
performanceCookiesOn: boolean;
};
interface State {
functionalCookiesOn: boolean;
performanceCookiesOn: boolean;
};
// Initialise context storage
const CookiesContext = React.createContext({});
const { Consumer: CookiesConsumer } = CookiesContext;
class CookiesProvider extends Component<Props, State> {
constructor(props: Props) {
super(props);
const {
functionalCookiesOn,
performanceCookiesOn,
} = this.props;
this.state = {
functionalCookiesOn,
performanceCookiesOn,
};
}
componentDidUpdate(_prevProps: Props, prevState: State) {
const {
functionalCookiesOn,
performanceCookiesOn,
} = this.state;
// Set cookie to store functional cookies setting
if (functionalCookiesOn !== prevState.functionalCookiesOn) {
Cookies.set('functionalCookiesOn', functionalCookiesOn.toString());
}
// Set cookie to store performance cookies setting
if (performanceCookiesOn !== prevState.performanceCookiesOn) {
Cookies.set('performanceCookiesOn', performanceCookiesOn.toString());
}
}
toggleAllCookies = () => {
// Store reversed state for functional and performance cookies
this.setState((prevState: State) => ({
functionalCookiesOn: !prevState.functionalCookiesOn,
performanceCookiesOn: !prevState.performanceCookiesOn,
}));
}
render() {
const { children } = this.props;
const {
functionalCookiesOn,
performanceCookiesOn,
} = this.state;
const value = {
functionalCookiesOn,
performanceCookiesOn,
toggleAllCookies: this.toggleAllCookies,
};
return (
<CookiesContext.Provider value={value}>
{children}
</CookiesContext.Provider>
);
}
}
export default CookiesContext;
export { CookiesConsumer, CookiesProvider };
When I use this in another function component it looks like this:
const AnotherComponent = () => {
const {
functionalCookiesOn,
performanceCookiesOn,
toggleAllCookies,
} = useContext(CookiesContext);
return (
...
);
}
This throws errors such as:
Property 'functionalCookiesOn' does not exist on type '{}'.
This seems to me to do with the following line in the original file:
const CookiesContext = React.createContext({});
Because I initialise the context with an empty object (because at that stage it's got no values).
What's the correct way to initialise this or apply types to avoid this error?
I think you can provide a type to your call to createContext
const CookiesContext = React.createContext<Partial<Props>>({});
<Partial> allows you to create the context without default values.
Try this out;-
import Cookies from 'js-cookie';
import React, { Component, ReactNode } from 'react';
interface Props {
children: ReactNode | ReactNode[];
functionalCookiesOn: boolean;
performanceCookiesOn: boolean;
};
interface State {
functionalCookiesOn: boolean;
performanceCookiesOn: boolean;
};
// Initialise context storage
const CookiesContext = React.createContext({
functionalCookiesOn: false,
performanceCookiesOn: false,
toggleAllCookies: () => null
});
const { Consumer: CookiesConsumer } = CookiesContext;
class CookiesProvider extends Component<Props, State> {
constructor(props: Props) {
super(props);
const {
functionalCookiesOn,
performanceCookiesOn,
} = this.props;
this.state = {
functionalCookiesOn,
performanceCookiesOn,
};
}
componentDidUpdate(_prevProps: Props, prevState: State) {
const {
functionalCookiesOn,
performanceCookiesOn,
} = this.state;
// Set cookie to store functional cookies setting
if (functionalCookiesOn !== prevState.functionalCookiesOn) {
Cookies.set('functionalCookiesOn', functionalCookiesOn.toString());
}
// Set cookie to store performance cookies setting
if (performanceCookiesOn !== prevState.performanceCookiesOn) {
Cookies.set('performanceCookiesOn', performanceCookiesOn.toString());
}
}
toggleAllCookies = () => {
// Store reversed state for functional and performance cookies
this.setState((prevState: State) => ({
functionalCookiesOn: !prevState.functionalCookiesOn,
performanceCookiesOn: !prevState.performanceCookiesOn,
}));
}
render() {
const { children } = this.props;
const {
functionalCookiesOn,
performanceCookiesOn,
} = this.state;
const value = {
functionalCookiesOn,
performanceCookiesOn,
toggleAllCookies: this.toggleAllCookies,
};
return (
<CookiesContext.Provider value={value}>
{children}
</CookiesContext.Provider>
);
}
}
export default CookiesContext;
export { CookiesConsumer, CookiesProvider };
This should do the trick and typescript will start intellisense.
I'm currently doing a setup of an ngrx app. One of the goal is to use ngrx to train myself even if the application is too small.
It's a small project for beer inventory management.
I've a component in which I want to display my beer list:
export class BeerListComponent implements OnInit {
availableBeers$: Beer[];
constructor(private breadcrumbService: BreadcrumbService,
private beerService: BeersService,
private store: Store<fromBeer.BeerState>) {
this.breadcrumbService.setItems([
{ label: 'Beers' },
{ label: 'List' }
]);
}
ngOnInit() {
this.store.select(fromBeer.getAvailableBeers).subscribe((beers: Beer[]) => {
console.log(beers);
this.availableBeers$ = beers;
})
console.log('fetching beers');
this.beerService.fetchBeersList();
}
}
my beer service does the following:
export class BeersService {
private firebaseSubscription: Subscription[] = [];
constructor(
private db: AngularFirestore,
private store: Store<fromBeers.BeerState>) { }
fetchBeersList() {
this.firebaseSubscription.push(this.db
.collection('beers')
.valueChanges()
.subscribe((beers: Beer[]) => {
console.log("Received a firebase change");
console.log(beers);
this.store.dispatch(new SetAvailableBeers(beers));
}, error => {
console.log(error);
}));
}
}
and here are my reducer/actions:
Actions
export enum BeersActionTypes {
SET_AVAILABLE_BEERS = '[Beers] SET_AVAILABLE_BEERS'
};
export class SetAvailableBeers implements Action {
readonly type = BeersActionTypes.SET_AVAILABLE_BEERS;
constructor(public payload: Beer[]) {
console.log(payload);
}
}
export type BeersActions
= SetAvailableBeers;
Reducer
export interface BeerState {
availableBeers: Beer[];
};
const initialState: BeerState = {
availableBeers: []
};
export function beersReducer(state = initialState, action: BeersActions): BeerState {
switch (action.type) {
case BeersActionTypes.SET_AVAILABLE_BEERS: {
console.log("Setting available beerse")
return {
...state,
availableBeers: action.payload
};
}
default: {
return state;
}
}
}
export const getAvailableBeers = (state: BeerState) => state.availableBeers;
What I cannot understand:
I receive beers from firebase, but my component never gets the update. If I check the chrome dev tools, I only get one this undefined but this doesn't get updated after.
Here are my logs:
I feel I did not register correctly to my ngrx state, but I cannot figure out what I did wrong?
I think I found my error!
I was subscribing directly to the beerReducers.getAvailableBeers and not the one of my app.reducer(which contains the CreateSelector and stuff)
I am using ngrx in an Angular 6 app and I can successfully load data from a database and display it on the screen. However, when I navigate to a new page in the app and then navigate back, the state is not preserved and it appears the #Effect is called again and loads the data from the database again. Here is my code:
Effect
export class ProductEffects {
constructor(private productService: ProductService, private actions$: Actions) { }
#Effect()
loadProducts$: Observable<Action> = this.actions$.pipe(
ofType(productActions.LOAD_ACTION),
switchMap(action =>
this.productsService.getProductDetails().pipe(
map(product => new productActions.LoadSuccess(product)),
catchError(err => of(new productActions.LoadError(err)))
)
)
);
Reducer
export interface ProductState {
product: Product;
}
const initialState: ProductState = {
product: {}
};
export function reducer(state = initialState, action: Action) {
switch (action.type) {
case SET_PRODUCT:
return { ...state, product: (action as SetProductAction).payload };
case LOAD_SUCCESS:
return { ...state, product: (action as LoadSuccess).payload, error: '' };
default: return state;
}
}
Actions
export const SET_PRODUCT = '[Product] Set Product';
export const LOAD = '[Product] Load';
export const LOAD_SUCCESS = '[Product] Load Success';
export const LOAD_ERROR = '[Product] Load Error';
export class SetProductAction implements Action {
readonly type = SET_PRODUCT;
constructor(public payload: Product) { }
}
export class Load implements Action {
readonly type = LOAD;
}
export class LoadSuccess implements Action {
readonly type = LOAD_SUCCESS;
constructor(public payload: Product) { }
}
export type ProductActions = SetProduct | Load | LoadSuccess;
Store
export interface State extends fromRoot.AppState {
product: fromProduct.ProductState;
}
// selectors
const getProductState = createFeatureSelector<fromProduct.ProductState>('product');
export const getProduct = createSelector(
getProductState,
state => state.product
}
Component
products$: Observable<Product>;
constructor(private store: Store<fromProduct.State>) { }
ngOnInit() {
this.store.dispatch(new Load());
this.products$ = this.store.pipe(select(fromProduct.getProduct));
}
Your ngrx implementation seems all ok to me. What you describe is an absolutely normal behavior. As soon as you navigate away from a page, the component destroyed and the new one is created, i.e. the ngOnInit is called. If you put the Loading logic in the ngOnInit, the state is loaded everytime you navigate to this page.
I am trying to use httpclient get method in angular 2 and show items in list ,But I am using ngrx with ngrxeffect(for asyn task).
I make a reducer , action .I also make effect file .Now I want to call service and fetch data from service and store data in store.
I tried like this
https://stackblitz.com/edit/angular-s5xdfv?file=src%2Fapp%2Fstore%2Flist.effect.ts
reducer
import * as Action from './list.action';
export interface Bank {
seo_val:string;
text_val:string;
}
export interface State {
isLoading: boolean;
isLoadSuccess: boolean;
banks: Array<Bank>;
}
export const initialState: State = {
isLoading: false,
isLoadSuccess: false,
banks: []
};
export function reducer(state = initialState, action: Action.Actions): State {
switch (action.type) {
case Action.GET_BANKLIST: {
return {
...state,
isLoading: true,
isLoadSuccess: false,
banks: []
};
}
case Action.GET_BANKLIST_SUCCESS: {
return {
...state,
isLoading: false,
isLoadSuccess: true,
banks: action.payload.data
};
}
case Action.GET_BANKLIST_FAILED: {
return {
...state,
isLoading: false,
isLoadSuccess: false,
banks: []
};
}
default: {
return state;
}
}
}
effect
import {Actions, Effect} from '#ngrx/effects';
import { HttpClient } from '#angular/common/http';
import {Injectable} from '#angular/core';
import {SwitchMap } from 'rxjs/operator ';
import * as BankListAction from './list.action';
#Injectable()
export class ListEffects {
#Effect()
authSingup = this.actions$
.ofType(BankListAction.GET_BANKLIST).
SwitchMap(()=>{
return this.http.get('http://biz.timesofindia.indiatimes.com/bankifsc/getlist')
})
constructor(private actions$: Actions,
private http:HttpClient) {
}
}
any udpate