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() {
}
}
A few things look out of order here.
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:
provide the service on app level, not on the component (or lazy module) level, if at all possible.
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).
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)
).
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With