rxjs observable: Using some kind of await? - javascript

I've added an interceptor for my HTTP requests where I have to use the access token of my user instance. In my app component I initialise my user:
app.component.ts
private async restoreUser(): Promise<UserModel | any> {
// ... some view stuff
return this.userService.restore()
// login instance could be found
.then(async () => {
// ... some view stuff
})
// local storage is empty -> login is necessary
.catch(async () => {
// ... some view stuff
this.subscription = this.networkSrv.getNetworkStatus()
.subscribe((status: ConnectionStatus) => {
if (status === ConnectionStatus.Online) {
// ... some view stuff
} else {
// ... some view stuff
}
});
});
}
http.interceptor.ts
return this.userSrv.user.pipe(
map((user: UserModel) => request.clone(
{setParams: {'access-token': user.accessToken}}
)),
mergeMap(request => next.handle(request))
);
Now I would like to do a request by initialising my app. The problem is, that the user instance is empty and the application throws an error. Is there a way to do something like await -> so that the user instance is set?
Example:
this.transmissionSrv.restoreQueue().then((projects: ProjectModel[]) => {
this.transmissionSrv.transmitProjects(projects, true).subscribe(console.log);
});
Currently, I use the setTimeout-method, but that isn't the way I should do it, right? In addition, sorry for not being consistent by using Observer; Ionic often uses Promises(?)

You should try adding a filter before your map. Using the filter, your map wont get call until the user is set.
return this.userSrv.user.pipe(
filter(Boolean),
map((user: UserModel) => request.clone(
{setParams: {'access-token': user.accessToken}}
)),
mergeMap(request => next.handle(request))
);

There are a couple of ways you could solve this.
Synchronously: Use an Angular APP_INITIALIZER (see here) to make the backend call and ensure the user object is present when the app bootstraps.
Asynchronously: Modify your existing application to store the user instance in an RxJs BehaviorSubject in a service somewhere and have components that depend on it subscribe to that BehaviorSubject wherever the user instance is needed. When the service constructs, have it make the backend call and stick the completed user instance inside the BehaviorSubject (userSubject.next(user)) when it's complete.

Related

tap is not waiting for response from backend service call in angular

how to subscribe to response when tap operator is used in the service.
Have anyone know how to resolve this?
edit(status) {
dataObj.val = status;
// call post service with status..
this.service
.update(dataObj)
.pipe(takeUntil(this._ngUnsubscribe$))
.subscribe(() => {
//i would like to wait until response come from backend and then navigate to the page so i get data over there.
if (res.status === 'Success') {
this.router
.navigate(['../../success'], {
relativeTo: this.route,
})
.then(() => {});
} else {
this.location.back();
}
});
}
//akita store service
update(
obj: any,
): Observable < any > {
return this.service.update(obj).pipe(
delay(800),
map((data: RestfulResponse < any > ) => data.data),
tap((data: anny) => {
this.store.update((state) => {
state.updateValue = data; // value is not updating and it is navigating to route
});
}),
);
}
//post service
update(obj){
//post call
}
Is there any way I can use tap and in service side and subscribe on component side?
I know I can use finalize but it is not helping for writing conditions inside.
The tap operator, by design, handles side effects which don't happen within the context of your observable pipeline. This means that your pipeline will never wait for results from the tap itself. I don't recommend using it in this manner. Under most circumstances, I only use tap for debugging.
If you are waiting for a particular state change, you should create a separate observable, selecting from your store, to watch the state for the expected change.
If you want to trigger an additional action when something happens, I recommend using ngrx Effects to achieve this.
Have a look at this post, where I talked about how to implement a similar use case:
https://stackoverflow.com/a/64491398/166850
You should also strive to set up reducers that apply your state changes, rather than updating the store directly.
Consider each of the following as a separate concern that you can implement independently of the others:
When user does an edit, trigger an edit action.
The reducer should update the state based on the edit action (for example, to show that a save is in progress)
When the edit action is triggered, trigger an effect. The app should make an HTTP call to save the change, then trigger a save finished action.
When the save is finished, the router navigation should be triggered.
This separates your code into multiple units which are easy to test and verify independently.
If #1 produces an action which is consumed by your reducer (#2), you can also create an ngrx Effect for #3 which listens for the same action, handles the HTTP call using switchMap, then triggers another action to signal that it's done.
Edit
Here's a simple example. The first time an action called APP_LOADED is triggered (from the AppComponent), this Effect makes an HTTP call to get data from the server, then triggers an action using the response data as the action payload.
The actual HTTP call is delegated to another service, the HttpMyConfigDataService, which simply calls HttpClient and returns an Observable.
#Injectable({
providedIn: 'root'
})
export class LoadMyConfigEffect {
constructor(
private httpMyConfigDataService: HttpMyConfigDataService,
private action$: Actions
) {
}
loadMyConfigData$ = createEffect(() => {
return this.action$.pipe(
filter((action) => action.type === 'APP_LOADED'),
take(1),
switchMap(() => this.httpMyConfigDataService.get().pipe(
map(data => {
return {type: 'MY_CONFIG_DATA_LOADED', payload: data};
}),
catchError(err => {
console.error('Error loading config data.', err);
return of({type: 'CONFIG_LOAD_ERROR', payload: err.message, isError: true);
})
))
);
});
}

How to stop code execution after routing to error page from vuex?

I have a cart page written with VueJs and Vuex. I have an api file that acts as a wrapper around axios.
In my vuex action, I call the API and if it was successful I commit data into my mutation.
async await mounted () {
const result = this.getStatus()
if (result === "locked") {
this.$router.push({name: "LockedPage"}
}
else if (result === "expired") {
this.$router.push({name: "SessionExpiredPage"}
}
doSomething(result)
},
methods: {
function doSomething(res) {
// does something with result
}
}
The getStatus function here is from my vuex action.
const {authid, authpwd} = router.history.current.params
if (!authid || !authpwd) {
router.push({name: "SomethingWrong"})
return
}
const res = await api.getStatus
commit("SET_STATUS", res.status)
if (res.otherLogic) {
//commit or trigger other actions
}
return status
}
What's the ideal way to handle these kind of API errors? If you look you'll see that I'm routing inside the Outer component's mounted hook as well as inside the vuex action itself. Should all the routing for this status function just happen inside the vuex action?
I think how it is currently set up, when theSomethingWrong page get's routed. It'll return execution back to the mounted function still. So technically the doSomething function can still be called but I guess with undefined values. This seems kind of bad. Is there a way to stop code execution after we route the user to the error page? Would it be better to throw an error after routing the page?
Should I use Vue.config.errorHandler = function (err, vm, info) to catch these custom route erorrs I throw from the vuex action?
For general errors like expired session I would recommend handle it low at axios level, using interceptor. https://github.com/axios/axios#interceptors
Interceptors can be defined eg. in plugins. Adding Nuxt plugin as example (Vue without Nuxt will use little bit different plugin definition, but still it should be useful as inspiration) (Also window is access because snippet is from SPA application - no server side rendering)
export default ({ $axios, redirect }) => {
$axios.onError((error) => {
const code = parseInt(error.response && error.response.status)
if (code === 401) {
// don't use route path, because error is happending before transition is completed
if (!window.localStorage.getItem('auth.redirect')) {
window.localStorage.setItem('auth.redirect', window.location.pathname)
}
redirect('/login-page')
}
})
}

How to wait for a variable to set in angular

AppInitializer class
dataloadcomplete = false;
initializeApp() {
let promise = new Promise((resolve, reject) => {
Promise.all(promise).then(
(res) => { dataloadcomplete = true; resolve() },
(err) => { reject(); },
);
return promise;
}
CanactivateRoute
canActivate(route: ActivatedRouteSnapshot): Promise<Boolean> {
// if data load completed return true else false;
}
want to reinitialize the application on country change.
With the small amount of shown code, it's difficult to suggest the best course of action as there are several options.
First, in any case, be sure that the class that holds the data (Class 1 in your example) is an injectable service. Inject it into any component that needs to access the data.
If you need to load some data and wait to route to a specific component until that data is loaded, you can use route resolvers.
If you need to wait for any of the application to load before launching the application, you can use APP_INITIALIZER: Angular: How to correctly implement APP_INITIALIZER
If you want to better manage your application state, consider using the Redux library for Angular called NgRx. You can use it to manage the flow of data in your application.
If you have multiple HTTP calls, I think, you can use forkJoin to retrieve all the call into one point then use EventEmitter and catch the event from the function of class 2. no need to wait for setting the variable of class 1.
You can use also interval in Angular and wait until you get the true value of the variable of class 2.

React and Mobx - Load API data on load?

I have to validate that the user is logged in, using some token, that currently in the next example will already be set, for testing.
I have two options that I can think of.
Option 1
Do it on store's constructor:
export class MyStore {
#observable token = "sometoken";
#observable authenticated = false;
constructor() {
this.checkAuth();
}
#action
checkAuth() {
fetch("http://localhost:3001/validate/" + this.token)
.then(res => res.json())
.then(data => {
this.authenticated = data.validated;
});
// catch etc
}
}
Option 2:
Do it in my component's that uses the data, componentDidMount method.
Both of the ways work, but what is really the best practice to handle such state?
I would definitely go for the first option. If you don't always need the authentication - for example some parts are public - then just don't call this.checkAuth() in the store constructor. If all parts need authentication, then it looks good like this.
Option 2 should be avoided because that would make unnecessary roundtrips to the server to re-validate a token which was already validated. And in general MobX gives great tools to minimize the use of lifecycle methods and write a cleaner code.

RxJS (and Angular): Attempting to cache one-time HTTP requests still causes multiple requests

I'm still in the process of getting comfortable with RxJS, so this is potentially an easy question.
Currently I am attempting to lazily make an XHR request for some data that I only need to fetch once and then cache indefinitely while the page is open, and I think I'm on the right track by trying to leverage an AsyncSubject with the value emitted from Angular's HTTP client. What I have so far basically looks like this:
#Injectable()
class AuthService {
user$ = new BehaviorSubject(null);
// do .switchMap() so we reset when the auth'ed user changes
extraInfo$ = this.user$.switchMap(() => {
return this.http.get('/api/account').share();
});
constructor(http: HttpClient) { }
...
}
This almost works since the request isn't made until something subscribes to extraInfo$, and .share() should prevent additional requests being made when I have more than 1 observer on it.
However, if I unsubscribe to it and extraInfo$ becomes cold (since there are 0 subscribers), subscribing to it again causes an additional request to be made again.
Right now I'm tempted to override the ._subscribe() property on an AsyncSubject so that I can run the request when it gets its first observer, but that feels a bit too hackish.
If you want to perform a request and then cache the result for this on any subsequent subscription, you can do this a lot easier:
#Injectable()
class AuthService {
user$ = new BehaviorSubject(null);
extraInfo$ = this.user$.switchMap(() => {
return this.http.get('/api/account').shareReplay(1);
});
constructor(http: HttpClient) { }
...
}
By using shareReplay(1) you are fixing the problem you had with the share operator. They are both multicasting operators but with different properties. You want the operator to be repeatable and not retryable (checkout this article I wrote on the subject to help you http://blog.kwintenp.com/multicasting-operators-in-rxjs/ understanding what I mean).
Just remember, If you want to cache a certain observables result indefinitely, shareReplay is the one you need.
I came up with something eventually, the following works for what I need:
const lastFetchedInfo = new WeakMap();
#Injectable()
class AuthService {
user$ = new BehaviorSubject(null);
// do .switchMap() so we reset when the auth'ed user changes
extraInfo$ = this.user$.switchMap(user => !user ? Observable.empty() : new Observable((observer) => {
if (!lastFetchedInfo.has(user)) {
const subject = new BehaviorSubject(null);
lastFetchedInfo.set(user, subject.filter(o => o !== null));
this.http.get('/api/account').subscribe(info => subject.next(info));
}
const subscription = lastFetchedInfo.get(user);
return () => subscription.unsubscribe();
}));
constructor(http: HttpClient) { }
...
}
I decided to use a BehaviorSubject instead of AsyncSubjectsince I can expand to set intervals on refreshing the data from here if I need to.

Categories

Resources