Async side effect depends on another effect - ngrx

I have two side effects. One of them presents a global loading spinner and the other one dismisses the spinner again. For presenting and dismissing the loader I have created a LoadingService. Note that the presentLoader() and dismissLoader() methods are async. Reason is that the service encapsulates the LoadingController of the Ionic framework.
import { Injectable } from '#angular/core';
import { LoadingController } from '#ionic/angular';
#Injectable({
providedIn: 'root'
})
export class LoadingService {
private loader?: HTMLIonLoadingElement;
constructor(private loadingController: LoadingController) {
}
public async presentLoader(translationKey: string): Promise<void> {
if (this.loader) {
this.loader.message = translationKey;
return;
}
this.loader = await this.loadingController.create({
message: translationKey
});
await this.loader.present();
}
public async dismissLoader(): Promise<void> {
await this.loader?.dismiss();
this.loader = undefined;
}
}
In my Application i work with the RequestStarted, RequestSucceeded and RequestFailed Action naming pattern.
public updateMyThingsItem$ = createEffect(() => {
return this.actions$.pipe(
ofType(
MyThingsItemActions.updateMyThingsItemNameRequested,
MyThingsItemActions.updateMyThingsItemPurchasePriceRequested,
MyThingsItemActions.updateMyThingsItemPurchaseDateRequested
),
exhaustMap((action) =>
this.myThingsIntegrationService.patchMyThingsItem(action.myThingsItem.id, action.myThingsItem.changes).pipe(
map((myThingsItem) => MyThingsItemActions.updateMyThingsItemSucceeded({ myThingsItem })),
catchError((error: string) => of(MyThingsItemActions.updateMyThingsItemFailed({ error })))
)
)
);
});
Now I created a side effect to display a loader for a couple of RequestStarted actions and a side effect that hides the loader again for the related Succeeded and Failed actions. This looks something like this:
public presentLoadingSpinner$ = createEffect(() => {
return this.actions$.pipe(
ofType(
MyThingsItemActions.updateMyThingsItemNameRequested,
MyThingsItemActions.updateMyThingsItemPurchasePriceRequested,
MyThingsItemActions.updateMyThingsItemPurchaseDateRequested
),
exhaustMap(() => this.loadingService.presentLoader('loading...'))
);
}, {
dispatch: false
});
public hideLoadingSpinner$ = createEffect(() => {
return this.actions$.pipe(
ofType(
MyThingsItemActions.updateMyThingsItemSucceeded,
MyThingsItemActions.updateMyThingsItemFailed
),
exhaustMap(() => this.loadingService.dismissLoader())
);
}, {
dispatch: false
});
Now for my problem: Because the methods in my loadingService are async there is the possibility that the side effect to dismiss the loader is triggered before the presentLoader() method is completed and the loader does not yet exist in the DOM. This happens when the API is really quick or a client error (no network) happens and the Failure Action is dispatched before the loader is created.
I therefore realise that I now have depending effects. But how else could I solve the problem with a side effect that triggers async code?
I know that I could create a loading state with a isLoading flag. But I would have the same problem when creating the effect from a selector.
An other possibility would be to create an overlaying Loader Component in the AppComponent that is rendered on the isLoading flag. But then I would have to give up the LoadingController of the Ionic framework which I do not really like.
I would like to know how to solve these problems in general when calling a side effect method that is async.

Related

Need to show newly added records/rows in Angular11 using web api service without page refresh

Component.ts
ngOnInit() {
this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);
}
service.ts
constructor(private http: HttpClient) { }
public getEmp_ServiceFun(): Observable<Employee[]> {
return this.http.get<Employee[]>(this.serverUrl + 'employees')
.pipe(
catchError(this.handleError)
);
}
it seems a bit strange to me to run the ngoninit method again since it is meant to run only once. I would wrap the employeeservice method in an observable interval. dont forget to unsubscribe though. otherwise it would keep calling the getEmp_ServiceFun until the whole app closes
ngOnInit() {
interval(1000).pipe(
map(() => {this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);})}
this would replace the whole array instead of just adding to it though. I would take a second look at the getEmp_ServiceFun so that you can ask only for new data and not all data and then push it to the array.
edit: even better would be to not subscribe in the the map but move it to after the pipe. you might need to use a switchMap
I used setTimeout() to refresh a component, it is working fine, but now I just need to check, is it good practice or not?
ngOnInit() {
this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);
//refresh this component
this.timeout = setTimeout(() => { this.ngOnInit() }, 1000 * 1)
}

React Redux Thunk Pass This.Props Methods in Action Creator

I am using react with react-stepzilla with Redux , Redux-thunk the problem is i want to use jumpToState(n) method inside action creator. but i am not able to access this method inside redux action creator file.
Action File
export const checkUser = (username) => {
return (dispatch,getState)=>{
wApi({user:username}).then(response => {
dispatch({
type: ActionTypes.CHECK_USER_NAME,
payload:response
})
e.jumpToStep(1);//Here it is Stepzilla Method
}).catch(err => {})
}
}
getState() method only providing me state value which i declared in reducer.
console.log(getState)
userdetail:{
username:"USER1001"
usertype:"SUPER"
isactive:"YES"
}
Reducer File
const defaultState={
userdetail:{
username:""
usertype:""
isactive:""
}
}
const reducer =(state=defaultState,action)=>{
switch (action.type) {
case ActionTypes.CHECK_USER_NAME :
{
return {
...state,
userdetail:action.payload,
}
}
default:
return state
}
}
export default reducer;
CheckUserName.js File Code
componentWillMount() {
this.props.checkUser("USER1001")
//console.log(this.props)
//{Here in console Output i can see "jumpToState" method in this.props}
//this.props.jumpToStep(1);
}
I find the solution by passing whole this.props to action creator method.
this.props.checkUser("USER1001",this.props)
i want to ask there is any alternate method for achieving this. i am new to react
From the documentation of react-stepzilla:
stepzilla injects an utility method called jumpToStep as a prop into all your react step components
As it is normal function in your props, you can pass it to your action creator as an argument and use it there. Passing the whole this.props is not necessary.
this.props.checkUser("USER1001", this.props.jumpToStep)
export const checkUser = (username, jumpToStep) => {
return (dispatch,getState)=>{
wApi({user:username}).then(response => {
dispatch({
type: ActionTypes.CHECK_USER_NAME,
payload:response
})
jumpToStep(1);//Here it is Stepzilla Method
}).catch(err => {})
}
}

UI not update (have to click somewhere) & router not work in Angular2-Meteor

I cannot update the UI immediately after subscribing the data from database, I have to click somewhere
Also if I use the router to go to another page, it does not work
#Component({
selector: 'foo-component',
template: `
{{foo}}
`
})
export class FooComponent extends MeteorComponent {
foo: string ;
constructor() {
super();
this.subscribe('messages', () => {
// I cannot change foo value immediately, I have to click somewhere
this.foo = 'foo';
// Also if I use the router to go to another page, it does not work
// this.router.navigate(['Home']);
});
}
}
How to solve this?
Note the new angular2-meteor version autoBind is set to true by default. So you probably won't meet this issue again.
But you still need use NgZone in Accounts.changePassword or other similar Accounts.foo() functions.
This problem is because that part of code run out of Angular 2 zone, you need run it inside of zone to update UI immediately or use router to go to another page.
Where do these problems usually happen?
Most time you don't do this. So when do you need this? Usually in callback of Meteor functions:
this.autorun(() => {
// here you need
});
this.subscribe('messages', () => {
// here you need
});
this.call('newMessage', (error, result) => {
// here you need
});
Accounts.changePassword(currentPassword, newPassword, error => {
// here you need
});
How to solve?
Take
this.call('newMessage', (error, result) => {
this.router.navigate(['Home']);
});
for example, you need change to:
import { NgZone } from '#angular/core';
constructor(private zone: NgZone) {}
this.call('newMessage', (error, result) => {
this.zone.run(() => {
this.router.navigate(['Home']);
});
});
Is there a clean way?
Luckily, Angular2-Meteor helps you do this dirty work.
Check Angular2-Meteor API document, there is an autoBind parameter for this.subscribe and this.autorun.
So now you don't need use this.zone.run, instead you can just add a true:
this.autorun(() => {
// your code
}, true);
this.subscribe('messages', () => {
// your code
}, true);

React redux separation of concerns

I'm trying to build a simple app to view photos posted from nasa's picture of the day service (https://api.nasa.gov/api.html#apod). Currently watching for keypresses, and then changing the date (and asynchronously the picture) based on the keypress being an arrow left, up, right, or down. These would correspondingly change the date represented by a week or a day (imagine moving across a calendar one square at a time).
What I'm having trouble with is this: I've created an async action creator to fetch the next potential date - however I need to know the current state of the application and the keypress to retrieve the new date. Is there a way to encapsulate this into the action creator? Or should I put the application state where the exported action creator is called in the application so I can keep my action creator unaware of the state of the application? I've tried to do this by binding the keydown function in componentDidMount for the top level Component, but the binding to the application store doesn't seem to reflect the changes that happen in the reducer.
The async logic relying on redux-thunk middleware and q:
// This function needs to know the current state of the application
// I don't seem to be able to pass in a valid representation of the current state
function goGetAPIUrl(date) {
...
}
function getAsync(date) {
return function (dispatch) {
return goGetAPIUrl(date).then(
val => dispatch(gotURL(val)),
error => dispatch(apologize(error))
);
};
}
export default function eventuallyGetAsync(event, date) {
if(event.which == 37...) {
return getAsync(date);
} else {
return {
type: "NOACTION"
}
}
}
Here's the top level binding to the gridAppState, and other stuff that happens at top level that may be relevant that I don't quite understand.
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
actions.eventuallyGetAsync(event, gridAppState.date);
});
}
render() {
const { gridAppState, actions } = this.props;
return (
<GridApp gridAppState={gridAppState} actions={actions} />
);
}
}
App.propTypes = {
actions: PropTypes.object.isRequired,
gridAppState: PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {
gridAppState: state.gridAppState
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(GridActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
I've validated that the correctly modified date object is getting to the reducer - however the gridAppState seems stuck at my initial date that is loaded.
What is the right way to approach async logic in redux that relies on attaching event handlers and current application state? Is there a right way to do all three?
You should handle the event in your component and call the correct action depending on the key pressed.
So when you dispatch an async action you can do something like
export default function getNextPhoto(currentDate) {
return (dispatch) => {
const newDate = calculateNewDate(currentDate);
dispatch(requestNewPhoto(newDate));
return photosService.getPhotoOfDate(newDate)
.then((response) => {
dispatch(newPhotoReceived(response.photoURL);
});
};
}
You should handle the keypress event on the component and just dispatch your action when you know you need to fetch a new photo.
Your App would look like
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
if (event.which == 37) {
actions.getNextPhoto(gridAppState.date);
} else if (...) {
actions.getPrevPhoto(gridAppState.date);
}
// etc
});
}
}
By the way you re still missing your reducers that update your state in the Redux Store.

Is this a redux middleware anti-pattern? How to properly build async actions with middleware

Just built my first API Middleware and was just wondering where I'm suppose to chain promises for action creators that dispatch multiple actions. Is what I did an anti-pattern:
export const fetchChuck = () => {
return {
[CALL_API]: {
types: [ CHUCK_REQUEST, CHUCK_SUCCESS, CHUCK_FAILURE ],
endpoint: `jokes/random`
}
}
}
export const saveJoke = (joke) => {
return { type: SAVE_JOKE, joke: joke }
}
export const fetchAndSaveJoke = () => {
return dispatch => {
dispatch(fetchChuck()).then((response) => {
dispatch(saveJoke(response.response.value.joke))
})
}
}
Should fetchAndSaveJoke dispatch the section action in my react component or is it okay to have it as its own action creator?
I would say that at this point in the Redux world, it's not super clear what's best practice and what the anti-patterns are. It's a very unopinionated tool. While that's been great for a diverse ecosystem to flourish, it does present challenges for people looking for ways to organize their apps without running into pitfalls or excessive boilerplate. From what I can tell, your approach seems to be roughly in line with the advice from the Redux guide. The one thing that looks funny to me is that it seems like CHUCK_SUCCESS should probably make SAVE_JOKE unnecessary.
I personally find it rather awkward to have action creators dispatch more actions, and so I worked out the approach behind react-redux-controller. It's brand new, so it's certainly not a "best practice", but I'll throw it out there in case you or someone else wants to give it a try. In that workflow, you'd have a controller method that looks something like:
// actions/index.js
export const CHUCK_REQUEST = 'CHUCK_REQUEST';
export const CHUCK_SUCCESS = 'CHUCK_SUCCESS';
export const CHUCK_FAILURE = 'CHUCK_FAILURE';
export const chuckRequest = () => { type: CHUCK_REQUEST };
export const chuckSuccess = (joke) => { type: CHUCK_SUCCESS, joke };
export const chuckFailure = (err) => { type: CHUCK_FAILURE, err };
// controllers/index.js
import fetch from 'isomorphic-fetch'; // or whatever
import * as actions from '../actions';
const controllerGenerators = {
// ... other controller methods
*fetchAndSaveJoke() {
const { dispatch } = yield getProps;
// Trigger a reducer to set a loading state in your store, which the UI can key off of
dispatch(actions.chuckRequest());
try {
const response = yield fetch('jokes/random');
dispatch(actions.chuckSuccess(response.response.value.joke));
} catch(err) {
dispatch(actions.chuckFailure(err));
}
},
};

Resources