Looking at this authguard which is called from canactivate :
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private loginServicePOST:LoginService, private router:Router) { }
canActivate(next:ActivatedRouteSnapshot, state:RouterStateSnapshot) {
return this.loginServicePOST({...}).map(e => {
if (e) {
return true;
}
}).catch(() => {
return Observable.of(false);
});
}
}
This code is working and an http request is invoked to server.
Question :
This is a cold observable and no one .subscribes to it - so I don't understand how this post request is invoked and why.
subscribe must be written IMHO.
NB
I already know that canactivate can return bool/Promise<bool>/Observable<bool>
The router is subscribing to the observable returned by canActivate which invokes the observable returned by loginService(...).map(...)
Related
I am creating Login page and want to call /login and /send-otp api together by a single function.
I have already created a login and registration page.
My code :
user-data.service.ts :
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class UserdataService {
url= 'http://localhost:9197/register';
url2= 'http://localhost:9197/login';
url3= 'http://localhost:9197/send-otp';
constructor(private http:HttpClient) {}
saveRegistration(data:any){
return this.http.post(this.url, data);
}
loginDone(ldata:any){
return this.http.post(this.url2, ldata);
return this.http.post(this.url3, ldata);
}
}
How to call multiply api ??
loginDone(ldata:any){
return this.http.post(this.url2, ldata);
return this.http.post(this.url3, ldata);
}
Apply TypeScript alias definition to your code:
Here in a more simplificated way, you have a multiple return with an alias structure.
type multi={
x:PelotaAzul,
y:PelotaRugosa,
}
function multiple():multi{
let x:PelotaAzul=new PelotaAzul("jj",5);
let y:PelotaRugosa=new PelotaRugosa("s",3,6)
let ab : multi ={x,y};
return ab;
}
You can also return an array of the both parameters like this
loginDone(ldata:any){
return [this.http.post(this.url2, ldata),this.http.post(this.url3, ldata)];
}
I find creating aliases more elegant but for faster results do the second one.
You are probably miss thinking your problem.
First of all. A return statement will always stop the function execution, doesn't matters if theres 100 lines after it.
Second, your logic probably will be: call login first then after (using or not the response) call the send-otp.
Or, your logic might be: call login and send-otp concurrently, but the loginDone method should only return some valid state if both requests run fine.
That's why other answers are completely wrong.
You must cascade your observables using pipe and switchmap for example:
return of([this.url2, this.url3]).pipe(
switchMap((url) => this.http.post(url, ldata))
)
If you want to return multiple values, you can return like this.
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class UserdataService {
url= 'http://localhost:9197/register';
url2= 'http://localhost:9197/login';
url3= 'http://localhost:9197/send-otp';
constructor(private http:HttpClient) {}
saveRegistration(data:any){
return this.http.post(this.url, data);
}
loginDone(ldata:any){
return {url1: this.url, url2: this.url2, url3: this.url3};
}
}
If you want to call multiple api, check this one .
https://medium.com/bb-tutorials-and-thoughts/how-to-make-parallel-api-calls-in-angular-applications-4b99227e7458
I've made a facade service to avoid multiple calls to the API.
It call retrieveMyUser each time the request is made.
If the request has never been made it store the value usingBehaviorSubject. If it has already been made it take the value stored.
I want to clear the data of my BehaviorSubject in auth.service.ts when a user logout. My try to do that is that I call a clearUser() method from facade-service.ts.
facade-service.ts :
...
export class UserServiceFacade extends UserService {
public readonly user = new BehaviorSubject(null);
retrieveMyUser() {
console.log(this.user.value);
return this.user.pipe(
startWith(this.user.value),
switchMap(user => (user ? of(user) : this.getUserFromServer())),
take(1)
)
}
private getUserFromServer() {
return super.retrieveMyUser(null, environment.liveMode).pipe(tap(user => this.storeUser(user)));
}
public clearUser() {
console.log("cleared");
this.storeUser(null)
console.log(this.user.value); // Output null
}
private storeUser(user: V2UserOutput) {
this.user.next(user);
}
}
auth.service.ts :
...
logout() {
var cognitoUser = this.userPool.getCurrentUser();
if (cognitoUser) {
this.userServiceFacade.clearUser()
cognitoUser.signOut();
}
this._router.navigate(['/login']);
}
...
The method clearUser() in auth.service.ts is well called and print cleared correctly.
But when I login, after I logout the console.log(this.user.value); in retrieveMyUser still output the previous value. It was null when at logout though.
So, how do I clear BehaviorSubject cache or to reset BehaviorSubject from another service ?
There are many things in your code which sound weird at reading:
You shouldn't access immediately to the value of a BehaviorSubject without using the asObservable() as recommended by ESLint here.
Instead, you could use another variable which will keep the latest value for the user.
You should use the power of TypeScript in order to help you with types definition and quality code (in my opinion).
The use of a BehaviorSubject with a startWith operator can be simplified using a ReplaySubject with a bufferSize of 1 (replay the latest change)
Your subject acting like a source storage should be private in order to limit the accessibility from outside.
I took your code and make some updates from what I said above:
export class UserServiceFacade extends UserService {
private _user: V2UserOutput;
private readonly _userSource = new ReplaySubject<V2UserOutput>(1);
public get user(): V2UserOutput { // Use for accessing to the user data without the use of an observable.
return this._user;
}
constructor() {
super();
this.clearUser(); // It will make your ReplaySubject as "alive".
}
public retrieveMyUser$(): Observable<V2UserOutput> {
return this._userSource.asObservable()
.pipe(
switchMap(user => (user ? of(user) : this.getUserFromServer())),
take(1)
);
}
private getUserFromServer(): Observable<V2UserOutput> {
return super.retrieveMyUser(null, 'environment.liveMode')
.pipe(
tap(user => this.storeUser(user))
);
}
public clearUser() {
console.log('cleared');
this.storeUser(null);
}
private storeUser(user: V2UserOutput) {
this._user = user;
this._userSource.next(user);
}
}
Cheers!
Our AngularJS project had start it's long way to the modern Angular.
The ngMigration util recommend me to remove all the $rootScope dependecies because Angular doesn't contain a similar concept like $rootScope. It is pretty simple in some cases but I don't know what to do with event subscription mechanisms.
For example I have a some kind of Idle watchdog:
angular
.module('myModule')
//...
.run(run)
//...
function run($rootScope, $transitions, Idle) {
$transitions.onSuccess({}, function(transition) {
//...
Idle.watch(); // starts watching for idleness
});
$rootScope.$on('IdleStart', function() {
//...
});
$rootScope.$on('IdleTimeout', function() {
logout();
});
}
On which object instead of $rootScope I have to call the $on function if I want to get rid of the $rootScope?
UPD
The question was not about "how to migrate on Angular2 event system". It was about how to remove a $rootScope dependencies but keep a event system. Well it seems to be impossible.
I don't know what to do with event subscription mechanisms.
Angular 2+ frameworks replace the $scope/$rootScope event bus with observables.
From the Docs:
Transmitting data between components
Angular provides an EventEmitter class that is used when publishing values from a component. EventEmitter extends RxJS Subject, adding an emit() method so it can send arbitrary values. When you call emit(), it passes the emitted value to the next() method of any subscribed observer.
A good example of usage can be found in the EventEmitter documentation.
For more information, see
Angular Developer Guide - Observables in Angular
You can implement TimeOutService which will do the log out after x minutes (in this case 15 min) of inactivity or it will reset the timer after certain action(s).
import { Injectable, OnDestroy } from '#angular/core';
import { Router } from '#angular/router';
import { Observable, Subject, Subscription, timer } from 'rxjs';
import { startWith, switchMap } from 'rxjs/operators';
import { AuthService } from 'path/to/auth.service';
#Injectable()
export class TimeoutService implements OnDestroy {
limitMinutes = 15;
secondsLimit: number = this.limitMinutes * 60;
private reset$ = new Subject();
timer$: Observable<any>;
subscription: Subscription;
constructor(private router: Router,
private authService: AuthService,
) {
}
startTimer() {
this.timer$ = this.reset$.pipe(
startWith(0),
switchMap(() => timer(0, 1000))
);
this.subscription = this.timer$.subscribe((res) => {
if (res === this.secondsLimit) {
this.logout();
}
});
}
resetTimer() {
this.reset$.next(void 0);
}
endTimer() {
if (typeof this.subscription !== 'undefined') {
this.subscription.unsubscribe();
}
}
logout(): boolean {
this.authService.signOut().subscribe((res) => {
this.subscription.unsubscribe();
});
return false;
}
ngOnDestroy():void {
this.subscription.unsubscribe();
}
}
And in the AppComponent have listener which will reset timeout on certain actions
In case as bellow it listens for keyboard strokes, mouse wheel, or mouse click
constructor(
private timeoutService: TimeoutService
) {
}
#HostListener('document:keyup', ['$event'])
#HostListener('document:click', ['$event'])
#HostListener('document:wheel', ['$event'])
resetTimer () {
this.timeoutService.resetTimer();
}
I am new to Angular, JS, and observables. I have a typescript class called DataService. I want it to load a list of URLs from a JSON formatted local file, and then have some way to call those URLs (to a handful of REST APIs) and return observables. The problem I am having is my code is not waiting for the config file to be loaded before the REST API functions get called.
I thought I could have the DataService constructor load the configuration file, and then have unique functions for each REST API call, but that isn't working
my code:
export class DataService {
configFile
constructor(private http: HttpClient) {
this.http.get('/assets/restApiUrlListConfig.json').subscribe(config => {
this.configFile = config;
});
}
getUrlFromConfigFile(name: string): string {
...
this returns the URL from the config file
...
}
getUrlAData(): Observable {
return this.http.get( getUrlFromConfigFile('A') )
}
}
My other components have code like this:
export class SomeComponent implements OnInit {
someComponentAData
constructor(private data: DataService) { }
ngOnInit() {
this.data.getUrlAData().subscribe(
data => {
this.someComponentAData = data
}
)
}
I am getting an error that the observable returned from the dataservice is undefined. Which I believe is because the constructor hasn't finished loading the config file, which I think is why the function getUrlAData isn't returning anything.
I feel like I'm not correctly handling these async calls, but I'm at a loss for how to tell my code to :
create the data service object
load the data file before anything else can be done
allow the other functions to be called asyncronously AFTER the config file is loaded
Angular CLI: 6.2.3
Node: 8.12.0
OS: win32 x64
Angular: 6.1.8
Edit 1: attempting to implement suggested solution
My DataService
configFile
configObservable: Observable<any>;
someSubscribeObj
constructor(private http: HttpClient) {
this.someSubscribeObj = this.http.get('/assets/restApiUrlListConfig.json').subscribe(config => {
this.someSubscribeObj = undefined;
this.configFile = config;
});
}
getObsFromConfigFile(name: string): Observable<any> {
//...
if (this.configFile != undefined) {
console.log('this.restApiUrlListConfig[name]',this.configFile[name])
return of(this.configFile[name])
}
else
return of(this.someSubscribeObj.pipe(map(c => c[name])))
//this.configObservable
//...
}
getUrlAData(): Observable<any> {
return this.getObsFromConfigFile('A').pipe(mergeMap(url => this.http.get(url)))
}
My other component:
constructor( private data: DataService ) { }
ngOnInit() {
//this.data.loggedIn.pipe((p) => p);
this.data.getUrlAData().subscribe(
data => {
this.urlAData = data
}
)
}
I was unable to store the "subscribe" into the observable, so I created a generic Any type varable, but at runtime I get a problem with the pipe command:
TypeError: this.someSubscribeObj.pipe is not a function
at DataService.push../src/app/services/data.service.ts.DataService.getObsFromConfigFile
(data.service.ts:67)
at DataService.push../src/app/services/data.service.ts.DataService.getUrlAData
(data.service.ts:74)
Edit 2: the unfortunate workaround
I am currently using two nested subscriptions to get the job done basically
http.get(config_file_url).subscribe(
config => {
http.get( config['A'] ).subscribe( adata => { do things };
http.get config['B'].subscribe( bdata => {do things };
}
)
I feel like I should be able to use a mergeMap of some sort, but I couldn't get them to work as I thought they would.
You need to wait on that async call, I would use a flatmap to get the value out of an observable.
export class DataService {
configFile
configObservable: Observable<any>;
constructor(private http: HttpClient) {
this.configObservable = this.http.get('/assets/restApiUrlListConfig.json').pipe(
map(config => {
this.configObservable = undefined;
this.configFile = config;
return configFile;
})
);
}
getUrlFromConfigFile(name: string): Observable<string> {
...
return of(configFile[name]) if configFile is set else return configObservable.pipe(map(c => c[name]));
...
}
getUrlAData(): Observable<string> {
return this.getUrlFromConfigFile('A').pipe(map(url => this.http.get(url)))
}
}
Basically you want to store the observable and keep using it till it completes, after it completes you can just wrap the config in an observable. The reason for wrapping it is to make the interface consistent, otherwise you have to have an if before every get.
I've an authentication guard that checks results from a BehaviorSubject but, before checking it, I need to check chrome local storage that return values in a callback and if the token is invalid renew it and inform BehaviorSubject to allow specific route.
How can I check local storage in get function?
Follow the code to better understand the flow.
auth.guard.ts
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
return this.authService.isLoggedIn
.take(1)
.map((isLoggedIn: boolean) => {
if (isLoggedIn) {
return true;
}
this.router.navigate(['/login']);
return false;
});
}
}
auth.service.ts
export class AuthService {
private loggedIn = new BehaviorSubject<boolean>(false);
get isLoggedIn() {
// Here I need check Google Storage that return a callback
// The sintax for is chrome.storage.sync.get('keys', callback);
return this.loggedIn.asObservable();
}
}
If this is all you are doing then you don't need to use observables. You aren't doing anything asynchronous. You just want to check the current value of local storage to see if the user is still authenticated.
The CanActivate interface allows you to return Observable<boolean> | Promise<boolean> | boolean. So you could just do something like this:
auth.guard.ts
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
if(this.authService.isLoggedIn) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}
auth.service.ts
export class AuthService {
get isLoggedIn() {
return localStorage.getItem('my-auth-key');
}
}
If you need to expose the authentication state as an observable for other reasons in your app then you could set a timer when you put their session in local storage. It could either poll on an acceptable interval, set it to the absolute expiration of the session, or something like that depending on your authentication scheme. The basic concept is that the only way to make your localstorage "observable" is to use a timer or some event.
For one app I worked on we have a token with an absolute expiration that we refresh on its session half-life. When I put it in local storage I set a timer for its absolute expiration. When I refresh the token and put it back in storage I cancel the previous timer. If the refresh fails for an extended period then the timer will eventually fire. It will forcefully remove the session from local storage since it is expired and it will broadcast (ngrx in my case but BehaviorSubject in yours) that the session has expired.
If you want to have a route change trigger an auth check just to be sure then you could do this:
auth.service.ts
export class AuthService {
private loggedIn = new BehaviorSubject<boolean>(false);
get isLoggedIn() {
this.loggedIn.next(localStorage.getItem('my-auth-key'));
return this.loggedIn.asObservable();
}
}
Since it is a behavior subject you can push a new value to it before you return it.
EDIT
Since you mentioned that you have to do chrome.storage.sync.get and it is async you could do the following (I'm not doing anything with parsing whatever comes out of storage):
auth.service.ts
export class AuthService {
private loggedIn = new Subject<boolean>();
get isLoggedIn() {
chrome.storage.sync.get('my-auth-key', (isAuthenticated) => {
this.loggedIn.next(isAuthenticated);
});
return this.loggedIn.asObservable();
}
}
Note that I changed it to just Subject rather than BehaviorSubject. This will force the recipient to wait for the .next call. This assumes that you want to check the authentication state each time someone tries to subscribe.
The more elegant solution would be to subscribe to chrome.storage.onChanged to feed your BehaviorSubject. You would probably set that up in your constructor. Something like this... I have not used this api before so this is just the general idea... you may need to do null checks or do parsing or something... and if the values don't expire from the store then you would still need a timer to remove them to fire the change.
auth.service.ts
export class AuthService {
private loggedIn = new BehaviorSubject<boolean>(false);
constructor(){
chrome.storage.onChanged.addListener((changes, namespace) => {
this.loggedIn.next(change['my-auth-key'].newValue);
});
}
get isLoggedIn() {
return this.loggedIn.asObservable();
}
}
You could just create an observable out of chrome storage methods:
export class RxChromeStore {
get(key: string): Observable<any> {
return Observable.create(obs => {
let cb = (err, data) => { // use whatever the chrome storage callback syntax is, this is typical cb structure
if (err) {
obs.error(err);
} else {
obs.next(data);
}
};
chrome.storage.sync.get(key, cb);
}).first();
}
}
export class AuthService {
private loggedIn = new BehaviorSubject<boolean>(false);
get isLoggedIn() {
return Observable.zip(RxChromeStore.get('your-auth-key'), this.loggedIn.asObservable(), (chromeAuth, authSubj) => chromeAuth || authSubj);
}
}
your question is a little vague so I'm not sure what your exact goal is, but the basic point here is that you can always create an observable out of soemthing that is callback based using the create method, then you can treat it like any other observable:
RxChromeStore.get('auth-key').switchMap((auth) => (auth.invalid) ? this.http.get('reauthendpoint') : this.loggedIn.asObservable());
or whatever stream you need
You can use localStorage variable to store/fetch data.
Storing: localStorage.setItem(key,value);
Fetching: localStorage.getItem(key);
This localStorage variable is provided by typescript node_module which you will already been having in your angular project.