0

In my angular app, when I login in to the app, I get an access token which I need to use it in my API calls in other components (not parent/child relationship). I tried using Shared service but it's not working

Data Service:

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs/Subject';

@Injectable()
export class DataService {

  private userInfo = new Subject<any>();
  currentuserInfo$ = this.userInfo.asObservable();

  constructor() { }

  updateUserInfo(message: any) {
    this.userInfo.next(message)
  }

}

In my login component I have

this.dataService.updateUserInfo(data.userToken);

Other Component

this.dataService.currentuserInfo$.subscribe((data: any) => { 
  console.log(data);
});

Here I am unable to get the tocken that was updated with the login component.

In the shared Service I tried both Subject and Behavior Subject but both didn't work.

I made sure that I just mentioned this DataService as a provider only in app.module.ts. I didn't mentioned it in login component or in any other component

The Head Rush
  • 3,157
  • 2
  • 25
  • 45
indra257
  • 66
  • 3
  • 24
  • 50
  • You should start by **not** using any. Maybe it's just that you mispelt userToken. maybe it's something else. We can't know without all the relevant code. Post a stackblitz reproducing the problem. – JB Nizet Apr 17 '18 at 20:13
  • When I use BehaviorSubject, I am able to get the value in other component, but when I refresh the page, I am unable to get the value in other component – indra257 Apr 17 '18 at 20:20
  • Well that's normal. What you store in memory disappears when you refresh the page: the application restarts from scratch. If data needs to survive a refresh, it must be stored in a persistent location: a cookie or local storage for example. – JB Nizet Apr 17 '18 at 20:21

3 Answers3

0

the best way to add token in requests is to use interceptor

import { Injectable, Injector, EventEmitter } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { AuthService } from '../services/auth.service';

@Injectable()
export class HttpsRequestInterceptor implements HttpInterceptor {
    private actionUrl: string;

    constructor(
        private injector: Injector
    ) {
        this.actionUrl = 'https://test.com/dev';
    }


    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const _req = {
            url: this.actionUrl + req.url,
            headers: req.headers.set('Content-Type', 'application/json')
        };

        const user = this.injector.get(AuthService).getAuthenticatedUser();

        if (user) {
            user.getSession((err: any, session: any) => {
                if (session) {
                    const token = session.getIdToken().getJwtToken();
                    _req.headers = req.headers
                        .set('Content-Type', 'application/json')
                        .set('Authorization', token);
                }
            });
        }

        return next.handle(req.clone(_req));
    }
}

UPDATED:

if you need to create a shareable service - you can try to use the behavior subject. Please look at this example

UPDATED2:

if you want to use shareable data in spite of the page refreshing - you should use localstorage:

localStorage.setItem('str', 'whatever string'); // set string
localStorage.setItem('obj', JSON.stringify(obj)); // set object
const str = localStorage.getItem('str'); // get string
const obj = JSON.parse(localStorage.getItem('obj')); // get object
const str = localStorage.removeItem('str'); // remove 
Dmitry Grinko
  • 13,806
  • 14
  • 62
  • 86
  • It is not only about tocken. I get lots of other information in my Authentication service which I need to use it in other components. ex: I get staff Id, which I have to use it in other components – indra257 Apr 17 '18 at 20:15
  • @indra257 "... but when I refresh the page ..." - I updated my answer – Dmitry Grinko Apr 17 '18 at 20:49
  • I am currently already using sessionStorage as a backup plan. Do you think localStorage/SessionStorage is the only option – indra257 Apr 17 '18 at 20:50
  • It is not a good practice because of security but If you want to use this data after page refreshing, it looks like the only option – Dmitry Grinko Apr 17 '18 at 20:52
0

Can you show how you have been using BahviorSubject and explain what means that this is not working? I think that it should work.

First, you need to declare it somewhere in the service, for example

token: BegaviorSubject<type> = new BehaviorSubject(null);

Then somewhere where you receive that token you need to set next

this.token.next(receivedToken);

And finally, subscribe to it form other components

this.yourService.token.subscribe(token => {
   this.token = token;
}

If you have some errors please show it.

Stefan
  • 1,431
  • 2
  • 17
  • 33
  • It works fine when I use behaviorSubject. But its not a good solution, because when I refresh my page, I am unable to get that value in the other components. – indra257 Apr 17 '18 at 20:37
  • Why you can't get it in the other components? Do you receive that when your app initialize? You can consider setting it in local storage. – Stefan Apr 18 '18 at 07:04
  • Yes I receive it in the login service – indra257 Apr 18 '18 at 13:03
  • So then set it to next, and it must be available in all components – Stefan Apr 18 '18 at 13:04
0

@indira: As @JB Nizet has explained, on a page refresh, your whole application refreshes so whatever in your is contained in your subject, will be lost!

Now, a Simple subject should also have been working in your case, are you subscribing to the Subject inside a function which is definitely called? for example inside ngOnInit()

ngOnInit() {
    this.dataService.currentuserInfo$.subscribe((data) => {
      console.log("data subscribed", data);
    })
}

I tried on my machine and the above works fine.

Why was Subject not working?

A possible reason for the Simple Subject to fail could be: You are just testing the functionality of the Subject and not doing an actual login. And in your scenario, your login component is loading before other components which have actually done the subscription. But wait: If you have already pushed new data to the Subject before it has been subscribed, you won't get the data in the subscription. Only after the next this.dataService.updateUserInfo(data.userToken), you will get the values in the Subscription. If this is the case, then you can definitely use the BehaviorSubject.

As far as saving the state of the Subject is concerned, you have options like: cookies, localstorage.

for example: when you are updating the Subject from your login component, store the data in localstorage:

this.dataService.updateUserInfo(data.userToken)
localStorage.setItem('serviceData', String(data.userToken));

And then apart from listening to the Subject in your other components, you can also take out the value of the token localStorage.getItem('serviceData') upon ngOnInit()

I myself prefer localStorage over cookies. Cookies have a size and a number limit per domain. See this link for more info. Even localStorage has a size limit, but that's huge. Using cookies or localStorage can also be clear from this SO question. You can analyze your requirement and implement either of them.

Ashish Ranjan
  • 12,760
  • 5
  • 27
  • 51
  • So you are also suggesting to use localStorage in order to work if I refresh the page – indra257 Apr 17 '18 at 21:00
  • Yes, you have got to store the token somewhere, All the websites to that (I guess you want to validate your loggedIn state with the API on subsequent requests even if the user reloads the page.). You can also use cookies. Of course make sure that your token is `encrypted` e.g: `JWT` as suggested by @Dimitri Grinko. And clear the localStorage on a logout. Also its nice to do it at one place for example have a class which handles all the `localStotage` (Which will actually be your `session`) and set and fetch it inside your `HTTPService` class. – Ashish Ranjan Apr 17 '18 at 21:08
  • Please check the edited answer for why `Subject` might not have been working in your case. – Ashish Ranjan Apr 17 '18 at 21:25
  • In the above comment where I said, set and fetch at one place, I actually meant, set at one place for example: HTTPService class and of course you may need the session data in other components, so fetch it anywhere you want. – Ashish Ranjan Apr 17 '18 at 21:32