Conditonally returning Observable<Action> within Ngrx Effect - ngrx

I'm currently refactoring my code to include the ngrx store. To minimise the amount of API calls of my LoadDeals() action, I'm checking in an effect if the store is empty. Only if it is empty, go on and make an API call. I first tried to use this pattern found here on SO (https://stackoverflow.com/a/50527652/2879771).
I realised the downside is, that every LoadDeals() call is ignored if there is data in the store. To have the possibility of forcing a load, I included an optional boolean payload to the LoadDeals() class. If this is set to true, do call the API.
Here's my first try
#Effect() loadDeals$: Observable<Action> = this._actions$.pipe(
ofType<LoadDeals>(actions.LOAD_DEALS),
withLatestFrom(this._store.pipe(select(getDealsLoaded))),
switchMap(([action, hasLoaded]) => {
if (!hasLoaded || action.force) {
return this._deals.deals.pipe(
map(deals => new LoadDealsSuccess(deals)),
catchError(error => of(new LoadDealsFail(error)))
);
} else {
return of(new LoadDealsSkip());
}
})
);
But this yields the following error:
Argument of type '([action, hasLoaded]: [LoadDeals, boolean]) => Observable<LoadDealsSuccess | LoadDealsFail> | Observable<LoadDealsSkip>' is not assignable to parameter of type '(value: [LoadDeals, boolean], index: number) => ObservableInput<LoadDealsSuccess | LoadDealsFail>'.
Type 'Observable<LoadDealsSuccess | LoadDealsFail> | Observable<LoadDealsSkip>' is not assignable to type 'ObservableInput<LoadDealsSuccess | LoadDealsFail>'.
Type 'Observable<LoadDealsSkip>' is not assignable to type 'ObservableInput<LoadDealsSuccess | LoadDealsFail>'.
Type 'Observable<LoadDealsSkip>' is not assignable to type 'Iterable<LoadDealsSuccess | LoadDealsFail>'.
Property '[Symbol.iterator]' is missing in type 'Observable<LoadDealsSkip>'.
Here is my deals.actions.ts
import { Action } from '#ngrx/store';
import { ITmsCpRecord } from '#models/cp';
export enum DealsActionTypes {
LOAD_DEALS = '[Deals] Load Deals',
LOAD_DEALS_FAIL = '[Deals API] Load Deals Fail',
LOAD_DEALS_SUCCESS = '[Deals API] Load Deals Success',
LOAD_DEALS_SKIP = '[Deals Store] Load Deals Skip (cached)'
}
export class LoadDeals implements Action {
readonly type = DealsActionTypes.LOAD_DEALS;
constructor(public force: boolean = false) {}
}
export class LoadDealsFail implements Action {
readonly type = DealsActionTypes.LOAD_DEALS_FAIL;
constructor(public payload: any) {}
}
export class LoadDealsSuccess implements Action {
readonly type = DealsActionTypes.LOAD_DEALS_SUCCESS;
constructor(public payload: ITmsCpRecord[]) {}
}
export class LoadDealsSkip implements Action {
readonly type = DealsActionTypes.LOAD_DEALS_SKIP;
}
// action types
export type DealsAction = LoadDeals|LoadDealsFail|LoadDealsSuccess|LoadDealsSkip;
So I split it into separate effects listening to the same action but with a different filter operator. This works fine. Although I would like to not split it because it is some redundant code.
Does someone see my error? Cheers
#Effect() loadDeals$: Observable<Action> = this._actions$.pipe(
ofType<LoadDeals>(actions.LOAD_DEALS),
withLatestFrom(this._store.pipe(select(getDealsLoaded))),
filter(([action, hasLoaded]) => !hasLoaded || action.force),
switchMap(() => {
return this._deals.deals.pipe(
map(deals => new LoadDealsSuccess(deals)),
catchError(error => of(new LoadDealsFail(error)))
);
})
);
#Effect() loadDealsSkip$: Observable<Action> = this._actions$.pipe(
ofType<LoadDeals>(actions.LOAD_DEALS),
withLatestFrom(this._store.pipe(select(getDealsLoaded))),
filter(([action, hasLoaded]) => hasLoaded && !action.force),
switchMap(() => of(new LoadDealsSkip()))
);

You will probably not like this answer, but I would suggest to create 2 actions for this.
One load action like you already have, and another force load action.
This would also mean creating 2 effects, but in the second one you won't have to select from store.

Related

Selectors No overload matches this call

Here's my selector.ts
export interface ITestState {
value: number;
}
export interface IReduxState {
test: ITestState;
}
export const selectTest = (state: IReduxState) => state.test;
export const selectTestValue = createSelector(
selectTest,
(state: ITestState) => state.value
);
Ïf i try to use it in app.component.ts
Like so
constructor(private store: Store) {
this.vaschal$ = store.select(selectTestValue);
}
I get the following error
No overload matches this call.
Overload 1 of 9, '(mapFn: (state: object) => number): Observable<number>', gave the following error.
Argument of type 'MemoizedSelector<IReduxState, number, DefaultProjectorFn<number>>' is not assignable to parameter of type '(state: object) => number'.
Types of parameters 'state' and 'state' are incompatible.
Property 'test' is missing in type '{}' but required in type 'IReduxState'.
Overload 2 of 9, '(key: never): Observable<never>', gave the following error.
Argument of type 'MemoizedSelector<IReduxState, number, DefaultProjectorFn<number>>' is not assignable to parameter of type 'never'
angular version 11.2.4
What do i do wrong?
You need to inform the Store of the root store state:
constructor(private store: Store<IReduxState>) {
this.vaschal$ = store.select(selectTestValue);
}

Flow property is missing in mixed passed - despite the type annotation

I have the following in my declarations file (included in my [libs]):
export type EtlFieldNoIdxT = {
name: Name,
purpose: Purpose,
}
export type EtlFieldT = {
idx: number,
...EtlFieldNoIdxT
}
And the following in my use of the types:
export const createEtlField = (
etlFields: { [Name]: EtlFieldT },
newField: EtlFieldNoIdxT,
) => {
if (etlFields === {}) {
throw new Error({
message: 'Cannot create a new etlField with an empty etlFields',
});
}
const field: EtlFieldT = {
idx: maxId(etlFields, 'idx') + 1,
...newField,
};
const subject: Name = Object.values(etlFields).find(
(f) => f.purpose === 'subject', // <<< f.purpose "missing in mixed" error
).name; // <<< .name "missing in mixed" error
return newEtlField(field, subject);
};
Despite having annotated the input, can flow not infer the type of what Object.values would thus return?
Thank you in advance for pointing out my misunderstanding.
- E
If you check the declaration for Object.values you'll find that it returns an array of mixed:
static values(object: $NotNullOrVoid): Array<mixed>;
A quick google search came back with
https://davidwalsh.name/flow-object-values
So to solve your issue, you wrap Object.values(...) with any, and then inside your find arg you can type it as EtlFieldT and finally refine your type back to EtlFieldT after find.
const subject: Name = ((Object.values(etlFields): any).find(
(f: EtlFieldT) => f.purpose === 'subject',
): EtlFieldT).name;
Though you should be aware that find has the possibility of returning undefined. So to be sound, you should run the find, and declare subject if the value exists.

With NGRX 8 and createAction, how can I use strongly-typed #Effect?

I'm migrating a large codebase from NGRX 7 to 8.
My actions look like this:
export class DeleteRecordAction implements Action {
readonly type = ActionTypes.DELETE_RECORD;
constructor(public payload: IRecord);
}
export class DeleteRecordSuccessAction implements Action {
readonly type = ActionTypes.DELETE_RECORD_SUCCESS;
}
export class DeleteRecordFailureAction implements Action {
readonly type = ActionTypes.DELETE_RECORD_FAILURE;
}
and my effects look like this (note the strongly-typed return Observable):
#Effect() deleteRecord$: Observable<DeleteRecordSuccessAction | DeleteRecordFailureAction> =
this.actions$.pipe(
ofType<Actions.DeleteRecordAction>(ActionTypes.DELETE_RECORD),
switchMap((action) => this.recordService.deleteRecord(action.payload).pipe(
map((result) => new Actions.DeleteRecordSuccessAction()),
catchError((err) => of(new Actions.DeleteRecordFailureAction())),
)),
);
I'm trying to migrate the actions to use the new createAction syntax, which is easy enough:
export const DeleteRecordAction = createAction('[FOO] Delete Record', props<{ payload: IRecord }>());
export const DeleteRecordSuccessAction = createAction('[FOO] Delete Record Success');
export const DeleteRecordFailureAction = createAction('[FOO] Delete Record Failure');
but now my #Effect won't compile at the following line:
#Effect() deleteRecord$: Observable<DeleteRecordSuccessAction | DeleteRecordFailureAction> =
with the errors:
TS2749: 'DeleteRecordSuccessAction' refers to a value, but is being used as a type here.
TS2749: 'DeleteRecordFailureAction' refers to a value, but is being used as a type here.
I can remove the strong-typing from the #Effect return value and just use the generic Action, like this:
#Effect() deleteRecord$: Observable<Action> =
but then I lose the useful type-checking of the return values, thereby making the code more error-prone.
The new createEffect method doesn't seem to help. Is there a way to use NGRX 8 while retaining strong-typing on effects? Or should I just stick with NGRX 7 syntax?

Trigger a action upon success full response of another action ngrx

I created a ngrx effect that's responsible for sending a POST request to a back end. now i want to change the implementation little bit. if the post request is successful I want to trigger another action that responsible update my ngrx store. but I couldn't figure out how to do that.
I tried to use switch map and merge map but didn't work.
creatTour$ = createEffect(() =>
this.actions$.pipe(
ofType(TourAction.createTour),
mergeMap((action) => {
const payload = action.tour;
const endpoint = environment.urls.tour.replace("{tourProviderId}", "1");
const request = new ServiceRequest(endpoint, 'POST', payload, options);
return this.requestHandlerService.invoke(request).pipe(
map((sucessResponce) => {
if (sucessResponce.code === 200) {
TourListAction.updateTourList({ tour: [tour] }) // i want to trigger this action and
// below action
}
return TourAction.createTourSucess({ responce: sucessResponce.message })
}),
catchError(err => {
const errorMessage = JSON.stringify(err);
console.log(errorMessage);
return of(TourAction.createTourFail({ errorMessage }))
})
)
})
)
);
i tried this way too
return [TourAction.createTourSucess({ responce: sucessResponce.message }
TourListAction.updateTourList({ tour: [tour] })]
it throws this error
Property 'type' is missing in type '(({ responce: string; }
& TypedAction<"[Tour] Create Tour Success">) | ({ tour: Tours[]; } &
TypedAction<"[Tour List] Tour List Update">))[]' but required in type
'Action'.
is this the better way to do this.i saw there is new thing called entity should I use that for this implementation?
Why not update your state on the createTourSucess action?
You can also return multiple actions:
.pipe(
switchMap(() =>
return [
TourListAction.updateTourList({ tour: [tour] }) ,
TourAction.createTourSucess({ responce: sucessResponce.message })
]
)
)
https://medium.com/#amcdnl/dispatching-multiple-actions-from-ngrx-effects-c1447ceb6b22
You can return multiple actions from your effect, they will all be dispatched.
See https://medium.com/#tanya/understanding-ngrx-effects-and-the-action-stream-1a74996a0c1c
For your code:
return successResponse.code === 200 ? [
createTourUpdateAction(tour),
TourAction.createTourSuccess
] : [TourAction.createTourSuccess]

How to subscribe to service that receives data from router (master-detail scenario)?

I am new to Angular2 and typescript, so this may be an easy one for any experienced folks! I develop a master-detail App using the Angular Heroes tutorial as a starter. I added a http service that calls a rest api. It properly lists decisions made in our town ( Antwerp Belgium :) ). Now when I click to view the details, the resulting json object is 'undefined'.
In my decision-detail.component.ts file, I have an error at decision => this.decision = decision, "Type 'Decision[]' is not assignable to type 'Decision'."
export class DecisionDetailComponent implements OnInit {
#Input() decision: Decision;
errorMessage: string;
constructor(
private _decisionService: DecisionService,
private _routeParams: RouteParams) {
}
ngOnInit() {
let id = +this._routeParams.get('id');
this._decisionService.getDecision(id)
.subscribe(
decision => this.decision = decision,
error => this.errorMessage = <any>error);
}
}
Here's my decision.service.ts code:
getDecision (id: number) {
return this.http.get(this._decisionsUrl+id)
.map(res => <Decision[]> res.json().data)
.do(data => console.log(data)) //eyeball results in the console
.catch(this.handleError)
Any help much appreciated!!
I got this working. Deleted the #input and declared decisions as a property in the class.

Resources