I'm using AngularFireAuth module to get the current user's UID to the component on page load through route resolving, but the lines of code needed to return the unwrapped promise to the component don't seem to be resolving and leave the promise unfulfilled.
relevant Routing Module Code for the edit-profiles module:
const routes: Routes = [
{
path: '',
component: EditProfilesComponent,
resolve: { userUID: UserInfoService },
},
];
The resolver service code is as follows:
export class UserInfoService implements Resolve<any> {
constructor(public afAuth: AngularFireAuth, public af: AngularFirestore) {}
async resolve() {
const user = await this.afAuth.currentUser;
const userUID = user?.uid;
return userUID; //returns undefined
}
}
I've also tried making it one promise and resolving it but that returned undefined as well. The code looked like this:
const promise = new Promise(async(resolve, reject) => {
const user = await this.afAuth.currentUser
const userUID = user?.uid;
resolve(userUID)
})
And then the EditProfiles consumer component code is:
export class UserInfoService implements Resolve<any> {
constructor(public afAuth: AngularFireAuth, public af: AngularFirestore) {}
async resolve() {
const user = await this.afAuth.currentUser;
const userUID = user?.uid;
return userUID; //returns undefined
}
}
I'm really not sure how to make it so that the promise returns the userUID to be used and consumed by that component, and display user-specific data, but I'm not sure how to make sure it's there before the component is instantiated.
#Bravo was right it was a problem with the design of the code it turns out what I actually wanted was for the page to wait until the promise was resolved to print out the data so what was needed on the ngOnInit was the following code
ngOnInit(): void {
this.afAuth.authState.subscribe((user) => {
if(user) {
//write logic here to make sure the promise gets resolved
} else {
//logic for when the page is getting the information
}
})
}
This is how you make sure that promises sent from firebase are resolved, you subscribe to the authstate observable and when that observable returns true, then you can finally retrieve the information from firebase that you need, that's the key piece that I was missing that it turns out that route resolvers may not have been the best tool for.
Related
I'm testing a React class component which relies on a service for retrieving the user auth.
async populateState() {
const result = await authService.getUserAuthenticationStatus();
const { user, isAuthenticated } = result;
this.setState({
isAuthenticated,
user
});
}
So, I would like to mock the return value for getUserAuthenticationStatus like so:
jest.mock('./components/api-authorization/AuthorizeService');
beforeAll(() => {
jest.spyOn(AuthService, 'getUserAuthenticationStatus').mockReturnValue(
Promise.resolve({
isAuthenticated: true,
user: {}
})
);
});
The problem here, is that when running my tests, the method keeps returning undefined rather than the mock value I had set up in my test. If we take a quick look at the exported member, we can see the class is being instantiated and then exported. Could this be the issue?
const authService = new AuthorizeService();
export default authService;
I think jest.mock is unnecessary here. You can simply spyOn the method since you already have an instance.
beforeAll(() => {
jest.spyOn(authService, 'getUserAuthenticationStatus')
// Returns promise no need to add manually
.mockResolvedValue({ isAuthenticated: true, user: {} });
});
// And make sure you clear the mocks in the end.
afterAll(() => jest.clearAllMocks());
Note: you are spying on the authService, not the AuthService
I have two server calls that I need to wait for. However, the user can decide if the second server call is even made. All of this is happening in an Angular Resolver, as the data is necessary for the next page.
The problem is that from my zip function the code never reaches the pipe, leaving me stuck in the resolver. Here is the procedure:
zip my requests
pipe them
and return an observable to my resolve function
this is my code:
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
Observable<any> | Promise<any> { //
const id = +route.paramMap.get('id');
const wantsSecondRequest = +route.paramMap.get('wantsSecondRequest');
const requests: Array<Observable<any>> = new Array();
requests.push(firstServerCallHappensHere());
if (wantsSecondRequest === 1) {
requests.push(secondServerCallHappensHere());
}
return zip(requests).pipe(take(1), mergeMap(([a, b]) => {
// DO STUFF WITH MY REQUESTS
return of(a);
}));
}
I tried it with Promise.all and working with Promises instead of Observables, but the issue with those are that I am unable to never complete the Promises in the case of an error, so that the navigation never happens.
According to the doc http://reactivex.io/documentation/operators/zip.html zip() accepts a number of parameters which are observables but you provide an array. It might help:
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
Observable<any> | Promise<any> { //
const id = +route.paramMap.get('id');
const wantsSecondRequest = +route.paramMap.get('wantsSecondRequest');
return zip(
firstServerCallHappensHere(),
wantsSecondRequest === 1 ? secondServerCallHappensHere() : undefined,
).pipe(take(1), mergeMap(([a, b]) => {
// DO STUFF WITH MY REQUESTS
return of(a);
}));
I'm trying to make a call to my backed, and it returns data just fine inside of my service, but when I try to use that service in a different module, it either logs undefined or Observable. Can anyone point me in a good direction? I've read about observables but I'm not 100% sure I'm getting them or if I even need one at this point. I was having problems with things on my page loading before I can tell them where to go which would throw an error.
Anyways here is the call to the backend, which logs out the game data as intended. This is inside my service.
async getAllGames() {
this.http.get(this.url).subscribe(gameData => {
console.log(gameData);
return gameData;
});
}
But when I call my service function from my other module, it returns undefined.
async getGames() {
const game = await this.games.getAllGames()
console.log(game);
_.each(game, (gameData) => {
this.gamesArray.push(gameData);
});
// this.loadCharts(this.gamesArray);
}
Don't use subscribe inside service. Just return an Observable from service then you can subscribe to that in any of your components you need.
Change your service code as follows.
getAllGames(): Observable<any> {
return this.http.get(this.url);
}
Then you can subscribe to that Observable any of your component as follows.
this.games.getAllGames()
.subscribe((res: any) => {
// TODO: do what you need with response data
});
PS
If you still need to subscribe inside service you should return a Promise.
getAllGames(): Promise<any> {
return new Promise((resolve, reject) => {
this.http.get(this.url).subscribe((res: any) => {
resolve(res);
}, (err: any) => {
reject(err);
})
});
}
Call the Promise from any of your components.
this.games.getAllGames()
.then(data => {
console.log(data);
})
.catch(err => console.error(err))
StackBlitz
I suggest such a solution, allowing you to get data in many components using the same service:
service:
class APIService {
private url: '...'
public getGames$;
constructor(private http: HttpClient) {
this.getGames$ = this.http.get(this.url);
}
}
component:
private games;
constructor(private api: APIService) {
this.service.getGames$.subscribe(games => {
this.games = games;
});
}
Using RxJs 5 and Angular 4.
I want to share an observable so that I only make 1 Http request, and I also want to await the calls so that I get the result when I request it. I have the following code:
export class DataService {
constructor(public http: HttpClient) {
this.getObservable();
}
public observable;
public getObservable() {
const url = "api/getData";
this.observable = this.http.get(`${this.baseUrl}${url}`).share()
}
public async hasData(data: DataEnum) {
const result = await this.observable.toPromise();
return result.filter(x => x === data).length > 0;
}
}
However many calls to hasData is resulting in many calls to our api end point. I am assuming that I have set observable to a shared observable, and when I call .toPromise() it will just get the cached value and make it a promise, which I can await.
Is this how it should work?
Due to how share works, observable is resubscribed on toPromise, ths produces new requests.
Promises already provide caching behaviour. Considering that promises are already using in service API, they can be used exclusively:
constructor(public http: HttpClient) {
this.getPromise();
}
public promise;
public getPromise() {
const url = "api/getData";
this.promise = this.http.get(`${this.baseUrl}${url}`).toPromise()
}
public async hasData(data: DataEnum) {
const result = await this.promise;
return result.filter(x => x === data).length > 0;
}
Your code seems overly complex to me. I would likely do something like:
private data = null;
getData():Observable<> {
// if data is already available, return it immediately
if (this.data) return Observable.of(this.data);
// else, fetch from the server and cache result
return this.http.get(url).do(data => this.data=data)
}
So whenever you want the data you just do:
this.getData().subscribe(
data => console.log(data);
)
To be sure that you won't call your API endpoints multiple times before the data arrives, you have a few options.
Look into data resolvers -- these will not init your component until the data has arrived. In ngOnInit the data will be ready synchronously so no risk of calling the server multiple times.
Alternatively, you can hide the view until the data is ready with *ngIf="data" so a user won't click a button multiple times.
I have created an authentication guard for my angular2 rc5 application.
I am also using a redux store. In that store I keep the user's authentication state.
I read that the guard can return an observable or promise (https://angular.io/docs/ts/latest/guide/router.html#!#guards)
I can't seem to find a way for the guard to wait until the store/observable is updated and only after that update return the guard because the default value of the store will always be false.
First try:
#Injectable()
export class AuthGuard implements CanActivate {
#select(['user', 'authenticated']) authenticated$: Observable<boolean>;
constructor() {}
canActivate(): Promise<boolean> {
return new Promise((resolve, reject) => {
// updated after a while ->
this.authenticated$.subscribe((auth) => {
// will only reach here after the first update of the store
if (auth) { resolve(true); }
// it will always reject because the default value
// is always false and it takes time to update the store
reject(false);
});
});
}
}
Second try:
#Injectable()
export class AuthGuard implements CanActivate {
#select(['user', 'authenticated']) authenticated$: Observable<boolean>;
constructor() {}
canActivate(): Promise<boolean> {
return new Promise((resolve, reject) => {
// tried to convert it for single read since canActivate is called every time. So I actually don't want to subscribe here.
let auth = this.authenticated$.toPromise();
auth.then((authenticated) => {
if (authenticated) { resolve(true); }
reject(false);
});
auth.catch((err) => {
console.log(err);
});
}
}
When you subscribe to an observable, you can provide a callback function; in the example below, I call it CompleteGet. CompleteGet() will only be invoked on a successful get that returns data and not an error. You place whatever follow on logic you need in the callback function.
getCursenByDateTest(){
this.cursenService
.getCursenValueByDateTest("2016-7-30","2016-7-31")
.subscribe(p => {
this.cursens = p;
console.log(p)
console.log(this.cursens.length);
},
error => this.error = error,
() => this.CompleteGet());
}
completeGet() {
// the rest of your logic here - only executes on obtaining result.
}
I believe you can also add a .do() to the observable subscription to accomplish the same thing.
all you need to do is force the observable to update:
canActivate(): Observable<boolean> {
return this.authenticated$.take(1);
}
Edit:
canActivate waits for the source observable to complete, and (most likely, I don't know what happens behind the scenes), the authenticated$ observable emits .next(), not .complete()
From documentation: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-take
.take(1) method takes first value emitted by the source observable and then completes
Edit2:
I just looked at snippet you pasted, and I was right - the store.select() observable never completes, it always emits .next
Subscribe doesn't return an Observable.
However, you can use the map operator like that:
this.authenticated$.map(
authenticated => {
if(authenticated) {
return true;
}
return false;
}
).first() // or .take(1) to complete on the first event emit