How to use an observable in angular 2 guards' canActivate() - javascript

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

Related

Rxjs - block subsequent requests while waiting for method to execute

I have a class, UserPermissionsService, that makes a server request when it's initially accessed. Once the request completes, subsequent calls use the data that's stored in a BehaviorSubject to complete their operations. However, if multiple happen simultaneously, the init method won't have completed and multiple server requests will occur.
How can I refactor the below class to force subsequent calls to wait for the initial init request to complete prior to executing?
#Injectable({
providedIn: 'root'
})
export class UserPermissionsService implements OnDestroy{
private _permissions = new BehaviorSubject<AppliedPermissions>(null);
public permissionSnapshot: AppliedPermissions;
public permissions: Observable<AppliedPermissions> = this._permissions.asObservable();
constructor(private _userService: UserService) {
}
init(): Observable<AppliedPermissions> {
return this._userService.getPermissions()
.pipe(tap(p => {
this._permissions.next(p)
this.permissionSnapshot = p;
}));
}
hasPermission(permission: string): Observable<boolean> {
return this._permissions.pipe(
switchMap(value => value ? of(value) : this.init()),
map(response => {
const perm = response.permissions
.find(el => el.permissionName === permission);
if (!perm)
return false;
return perm.allow;
}),
catchError(_ => of(false))
);
}
inRole(role: string): Observable<boolean> {
return this._permissions.pipe(
switchMap(value => value ? of(value) : this.init()),
map(response => {
return !!response.roles.find(el => el === role);
}),
catchError(_ => of(false))
);
}
ngOnDestroy(): void {
this._permissions?.complete();
}
}
did you consider OnInit()
export class App implements OnInit {
constructor() {
// Called first time before the ngOnInit()
}
ngOnInit() {
// Called after the constructor and called after the first ngOnChanges()
}
}
Now to your answer - try flatmap to preserve/control the sequence, this ensures that the order of execution is retained. for e.g. see here
first
.flatMap(() => second)
.flatMap(() => third)
.subscribe(()=> console.log('finished'));
Another option
// use this wrapper
function WaitForComplete<T>(signal: Observable<any>) {
return (sourceInit: Observable<T>) => signal.pipe(
first(), switchMap(_ => sourceInit),
);
}
// force the waiting to complete, by deferring the subscribe on the first source
// till the signal observable completes/emits (many versions of this out there)
var YourSecond =
anotherObservable.pipe(WaitForComplete(YourFirstObservableFunction), take(1));
Update: services can implement ngOnInit()
I would personally replace:
switchMap(value => value ? of(value) : this.init()),
wirth
filter(value=>!!value)
Assuming that you want to do the request only in initial init and just wait for it to finish

Call to backend works properly inside service, but not outside service

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;
});
}

Return a Observable from a Subscription with RxJS

I currently have a service that do a HTTP request to an API to fetch data. There are some logic that I want to do to the observable from within the service, but I still want to also subscribe to the Observable in my Component so that I can return any errors to the Component and to the user.
Currently I do:
// service.ts
getData(): Observable<any> {
return this.http.get(url).pipe(catchError(this.handleError)
}
// component.ts
ngOnInit() {
this.service.getData().subscribe(res => {
// Do my logic that belong to the service.ts
// ...
// Do my logic that belongs in the component.ts
// ...
}, err => this.errors = err)
}
What I would like to do is to refactor this so that I handle the logic related to the subscription and the service.ts within the getData() method, and then return an Observable with the HTTP response and any errors to the Component so that I can continue doing things there.
What's the pattern to do this?
I feel like multiple of the patterns and solutions posted is "ugly" or does not follow the Observable pattern (Like doing callbacks).
The cleanest, most "RxJS"-like solution I came up with was to wrap the service method's return value in a second Observable.
So the following:
// service.ts
getData(): Observable<any> {
return new Observable(subscriber => {
this.http.get(url)
.pipe(catchError(this.handleError)
.subscribe(res => {
// Do my service.ts logic.
// ...
subscriber.next(res)
subscriber.complete()
}, err => subscriber.error(err))
})
}
// component.ts
ngOnInit() {
this.service.getData().subscribe(res => {
// Do my component logic.
// ...
}, err => this.errors = err)
}
Use map:
// service.ts:
import { catchError, map } from 'rxjs/operators';
getData(): Observable<any> {
return this.http.get(url).pipe(
map(res => {
/* Your processing here */
return res;
}),
catchError(this.handleError)
)
}
Try this way
service.ts
getData(): Observable<any> {
return this.http.get(url).map(res=> <any>(res['_body']));
}
component.ts
this.service.getData().subscribe(response=>{
var res1 = JSON.stringify(response);
var res2 = JSON.parse(res1);
var res3 = JSON.parse(res2);
}); //parse response based on your response type
Option 1
If you subscribe Observable in component then only component will have that subscription and it must be passed back to service.
Option 2
Use this pattern.
service.ts
getData(doer: Function) {
let subscriptions = Observable.of({ data: 'response', isError: false })// request
.catch(error => Observable.of({ data: error, isError: true })) //handle error
.do(data => doer(data))
.subscribe();
this.handleSubscription(subscriptions); //subscription handling in service
}
component.ts
ngOnInit() {
this.getData(response => {
if (response.isError) {
///
} else {
let data = response.data;
// Process
}
})
}
Be careful: All the answers are for <= Angular 4. In Angular 5, you don't need a map() anymore, so just leave that out. just return this.http.get() as it returns an Observable, where you can subscribe on.
Furthermore, be aware you have to import HttpClient instead of Http.
You can directly use "map" and "catch" function on Observable returned by http.get method.
import { catchError, map } from 'rxjs/operators';
getData(): Observable<any> {
return this.http.get(url)
.map(res => {
/* Your processing here */
return res;
})
.catch(this.handleError);
}
You can remove this, and use map. In subscribe error, you can get error event.
If you use HttpClient, just use get!

Observable for mutiple responses in angular 2

So, I have this service which first calls a function from another module which basically returns an list of urls from an external api. This service then must http.get from all the urls in that list (every url returns a json object of same format) then return a single observable which I can then use in an angular component. Here's what my service code looks like:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Client } from 'external-api';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
let client = new Client();
#Injectable()
export class GbDataService {
constructor(private _http: Http) {
}
getGBData(): Observable<any> {
client.fetchUrls("").then(resp=> {
resp.forEach(url => {
//this._http.get(url).map(res => res.json);
// Create a single observable for every http response
});
}).catch(function(err){
console.log(err.message);
});
//return observable
};
}
http.get returns and Observable<Response> type but I couldn't find a way to create and return one Observable for all http.get responses. How can this be done ? Should I create an observable array then push() all the get response I get to the array?
EDIT: It doesn't really matters to me if responses are emitted one by one or all at once BUT there must be only a single Obeservable which emits the responses of all the http.get requests.
Further Edit: This is my fetchURLs method:
fetchURLs(): Promise<any> {
return new Promise((resolve, reject) => {
let _repos: Array<any>;
//scrapeTrendingRepos() is from https://github.com/rhysd/node-github-trend
scraper.scrapeTrendingRepos("").then(repos => {
repos.forEach(repo => {
let url = `https:api.github.com/repos/${repo.owner}/${repo.name}`;
_repos.push(url);
});
resolve(_repos);
}).catch(err => {
reject(err);
});
})
};
Have I implemented Promises in fetchURLs() right??
So, you make a request and get back an array of URLs that you then want to fetch all and get one response from?
Those are the types of things that RxJS excels at.
#Injectable()
export class GbDataService {
constructor(private _http: Http) {
}
getGBData(): Observable<any> {
return Observable
.fromPromise(client.fetchUrls()) // returns Observable<array>
.switchMap( urls => {
// convert your list of urls to a list of http requests
let urlRequests = urls.map( url => http.get(url) );
// combineLatest accepts an array of observables,
// and emits an array of the last results of each of the observables
// but the first emit only happens after every single observable
// has emitted its first result
// TLDR: combineLatest takes an array of Observables
// and will emit an array of those observable emissions // after all have emitted at least once
return Observable.combineLatest(urlRequests);
})
}).catch(function(err){
console.log(err.message);
});
//return observable
};
}
Further info:
Read up on the combineLatest observable. In this scenario, it accomplishes what you want of waiting for all its observable arguments to emit before emitting a single array. But if your observable arguments also emit multiple times, it may not do what you expect and you might want to try a different operator like forkJoin or zip.
Additionally
You might want to use switchMap rather than flatMap - if a new request for urls to fetch comes through, switchMap will cancel any requests currently in flight before sending the new list of requests.
Further Edit
Although your fetchURLs implementation can work in its current incarnation, you can simplify your method a bit if you wish by taking advantage of how promises work. In 'Promise-land', the then handler also returns a Promise, and that second Promise will resolve with whatever value you return from your then handler (this is the basic promise chaining concept). Using that knowledge, you can simplify your method to:
fetchURLs(): Promise<any> {
//scrapeTrendingRepos() is from https://github.com/rhysd/node-github-trend
return scraper.scrapeTrendingRepos("").then(repos => {
// since repos is an array, and you wish to transform each value
// in that array to a new value, you can use Array.map
return repos.map( repo => `https:api.github.com/repos/${repo.owner}/${repo.name}`);
});
}
if client.fetchUrls("") return a native Promise you may want to use snorkpete solution.
if not try to create an observable:
getGBData(): Observable<any> {
return Observable.create(observer => {
client.fetchUrls("").then(resp=> {
resp.forEach(url => {
this._http.get(url).map(res => res.json).subscribe(data=>{
observer.next(data);
});
});
}).catch(function(err){
console.log(err.message);
observer.error(err);
});
});
}

Correct way to unit-test observable stream being fed

I use this to test that the service adds an item to observable stream of errors.
it('can be subscribed for errors', () => {
let testError = new Error('Some error.');
let called = false;
let subscription = service.onError.subscribe(error => {
called = true;
expect(error).toEqual(testError);
});
/// This makes an error to be added to onError stream
service.setError(testError);
expect(called).toEqual(true);
});
I use the called variable to make sure that the subscription callback was actually called. Otherwise the test would pass when it shouldn't. But it doesn't seem right to me. Also, it wouldn't work if the stream was asynchronous.
Is this a good way to test that? If not, how to do it properly?
EDIT: this is the class that's being tested. It's in typescript, actually.
import { ReplaySubject } from 'rxjs/Rx';
export class ErrorService {
private error: Error;
public onError: ReplaySubject<Error> = new ReplaySubject<Error>();
constructor() {
}
public setError = (error: Error) => {
this.error = error;
console.error(error);
this.onError.next(error);
}
public getError() {
return this.error;
}
public hasError() {
return !!this.error;
}
}
The way you are testing is good. You are:
Checking if the value is correct with expect statement.
Checking the fact the expect statement is being executed.
Especially the last part is important otherwise the expect might not be triggered and the test will falsely pass.

Categories

Resources