Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Data saved in service lost on page refresh or change

I have an Angular 5 application, and I'm attempting to use a Google OAuth login to get a username and then set that username in a service to be used as the logged in user. Before I added the login I set the value manually in the service and it worked with no issue. Now I'm setting the username from the Google login it looks like the value I set is being lost each time the page refreshes (or when a new instance of the service is called).

The value returns correctly from the Google login (I checked in the console), so I know everything is ok there. My impression of Angular services was that they were constant across the other modules? Is it the case that each time I call that service it's creating a new empty 'tempuser' variable? If so is there any way around this so I can keep the value across the whole application until the user logs out?

This is the service itself:

import { Injectable } from '@angular/core';
import { Http, Response, Headers } from "@angular/http";

@Injectable()
export class WmApiService {

  //private _baseUrl = "http://wm-api.webdevelopwolf.com/"; // Test server api
  private _baseUrl = "http://localhost:58061/"; // Dev server api 
  tempuser = "";
  tempuseravatar = "";
  tempuserfullname = "";
  tempuseremail = "";
  userloggedin = 0;
  modules: any;

  constructor(private _http: Http) {
    console.log('Wavemaker API Initialized...');
  }

  // On successful API call
  private extractData(res: Response) {
    let body = res.json();
    return body || {};
  }

  // On Error in API Call
  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error);
    return Promise.reject(error.message || error);
  }

  // Basic Get W/ No Body
  getService(url: string): Promise<any> {
    return this._http
        .get(this._baseUrl + url)
        .toPromise()
        .then(this.extractData)
        .catch(this.handleError);
  }

  // Basic Post W/ Body
  postService(url: string, body: any): Promise<any> {
    console.log(body);
    let headers = new Headers({'Content-Type': 'application/json'});
    return this._http
      .post(this._baseUrl + url, body, {headers: headers})
      .toPromise()
      .then(this.extractData)
      .catch(this.handleError);
  }

}

And a straightforward example of where it would be called:

import { Component, OnInit } from '@angular/core';
import { WmApiService } from '../../wm-api.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  userSignedToJourney: boolean;

  constructor(private _wmapi: WmApiService) { }

  ngOnInit() {
    this.userRegisteredToJourney();
  }

  // Check if Trailblazer is already on Journey
  userRegisteredToJourney() {
    this._wmapi
    .getService("Journey/TrailblazerRegistered/" + this._wmapi.tempuser)
    .then((result) => {
      if (result == 1) this.userSignedToJourney = true; else this.userSignedToJourney = false;
    })
    .catch(error => console.log(error));
  }

}

And the temp user value is set as such:

import { Component, OnInit } from '@angular/core';
import { WmApiService } from '../wm-api.service';
import { Router } from "@angular/router";

declare const gapi: any;

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})

export class LoginComponent implements OnInit {

  constructor(private _wmapi: WmApiService, private router: Router) { }

  public auth2: any;
  userProfile: any;
  user: any;

  // Initalise Google Sign-On
  // NOTE: Currently registered to http://localhost:4200/ - will need to change when on server to final URL
  public googleInit() {
    gapi.load('auth2', () => {
      this.auth2 = gapi.auth2.init({
        client_id: '933803013928-4vvjqtql0nt7ve5upak2u5fhpa636ma0.apps.googleusercontent.com',
        cookiepolicy: 'single_host_origin',
        scope: 'profile email',
        prompt: 'select_account consent'
      });
      this.attachSignin(document.getElementById('googleBtn'));
    });
  }

  // Log user in via Google OAuth 2
  public attachSignin(element) {
    this.auth2.attachClickHandler(element, {},
      (googleUser) => {
        // Get profile from Google
        let profile = googleUser.getBasicProfile();        
        // Save user to the API until changed
        this._wmapi.tempuser = profile.getName().match(/\(([^)]+)\)/)[1];
        this._wmapi.tempuseravatar = profile.getImageUrl();
        this._wmapi.tempuserfullname = profile.getName();
        this._wmapi.tempuseremail = profile.getEmail();
        // Log the user in
        this._wmapi.userloggedin = 1;
        // Redirect to dashboard
        this.router.navigate(['/dashboard']);
      }, (error) => {
        alert(JSON.stringify(error, undefined, 2));
        // To get auth token - googleUser.getAuthResponse().id_token;
        // To get user id - profile.getId();
      });
  }

  ngAfterViewInit(){
    this.googleInit();
  }

  ngOnInit() {
  }

}
like image 951
Web Develop Wolf Avatar asked Oct 26 '18 10:10

Web Develop Wolf


2 Answers

A few things look out of order here.

  1. Dependency Injection wrong

You're injecting your WM API service into the component, and then configuring it there. This should not be the case, you should do this on the service itself, and on init. And the service should not be in a "ready" state until it gets this google API data. For one, your component should not be concerned with configuring services - it's only asking for the services and using them. Second, if you use service in other places, e.g. when there is no login component, who is then going to configure your service? And third, if you have multiple instances of the service, that likely means that you are doing it wrong - you should provide the services on global app level so that they all use the same instance, but even if not, you still need to have the service take care for its dependencies, not the service consumers.

Steps to remedy this specific part:

  • take out the gapi.load() etc stuff out of the component and put it in the service
  • provide the service on app level, not on the component (or lazy module) level, if at all possible.

    1. Page reload issue

Possibly some of this stuff is persistent - you say that on page reload, you lose stuff. That is only logical - on each page reload, the stuff from memory dissapears and you have a fresh new app. Perhaps you want to store things like JWT tokens and access stuff into sessionStorage or localStorage. If there is such stuff that needs to persist accross page reloads, you should also build and provide a storage service in your app that offers serialization/deserialization services to your WM API Service (and others). Again, WM Api service is injected with this storage so it can configure itself on startup (in it's constructor).

  1. Http wrong

Http, Headers, Response and basically the entire @angular/http is deprecated in Angular 4.3 - you should use HttpClient and friends from @angular/common/http. The change should be really simple and it's worth it. Also, try and ween yourself out of .toPromise() on the Http client and into the observables. It'll make working with other things (also observables) easier accross the app, and the change is also relatively minimal - Http(Client) observables complete anyway after success or failure, so your logic should still be the same (just use subscribe(successHandler, errHandler) instead of then(successHandler, errHandler)).

  1. Document

I see you are also using document.getElementById (and possibly other stuff). You would be much better off not to use the browser globals directly, and inject Angular-provided proxies instead. In the long run, you'll thank yourself you did this.

like image 77
Zlatko Avatar answered Oct 10 '22 11:10

Zlatko


Yes, you can have multiple instances of a service in Angular but not each time you call it. A service has same instance across same level of modules. Hence to have single instance of a service, provide it in app.module.ts. Moreover, any data stored in a service can get lost on refresh.You can use localStorage or sessionStorage for same purpose.

like image 5
Sarthak Aggarwal Avatar answered Oct 10 '22 13:10

Sarthak Aggarwal