I'm creating an application where the user can identify himself via his Google account. Behind the scenes, I'm using gapi to handle the login. On the other hand, there is an angular service called "user" that has an Observable
that is broadcasting information to subscribers every time the status (online/offline) of the user changes. Then, to glue everything together, there is a button and when the user clicks on it, here's what happen:
- A promise is created that resolves once gapi is initialized. (1st line of the code below)
- When the promise resolves, the Google login is performed via gapi. (2nd line of the code below)
- When the login promise resolves, an AJAX request id made to the backend to verify the Google token and returns information such as the email, the name, etc. and subscribe to this observable. (the line starting with
this.restService
in the code below) - When the observable above emits the value returned by my web service, I call a function in my "user" service that emits a value on the observable for the status of the user (it broadcast the fact that the user is now authenticated).
- Then, all subscribers receive this information and know that the user is logged in.
Here is the code:
this.ensureApiIsLoaded().then(() => {
this.auth.signIn().then((user) => {
let profile = user.getBasicProfile();
this.restService
.post("/api/security/authenticate", <IAuthenticationPayload>{ type: AuthenticationType.Google, token: user.getAuthResponse().id_token })
.subscribe((data: IUserData) => {
this.userService.set("data.name", "data.email", "data.picture", AuthenticationType.Google);
});
});
});
The thing is that code sometimes works and sometimes not. After some investigation, I noticed that it was related to the duration of the my web service execution. To ensure this, I made a statement inside it that pauses the execution of the request during 2 seconds et in that case, my code always fails. But what do I mean by "fails"?
Somewhere on a page, I have a component that is subscribed to the user service observable:
this.userService.getStatus().subscribe(status => {
console.log(status);
this.canPostComment = (status == UserStatus.Online);
});
When the web service executes very fast, I see the console.log
, then the canPostComment
property is updated and so is my view, so no issue. However, when the web service takes a bit longer, I still see the console.log
with the correct value but the view is not updated...
Suspecting that it has something to do with Angular changes detection, I use zone this way:
this.ensureApiIsLoaded().then(() => {
this.auth.signIn().then((user) => {
let profile = user.getBasicProfile();
this.zoneService.run(() => {
this.restService
.post("/api/security/authenticate", <IAuthenticationPayload>{ type: AuthenticationType.Google, token: user.getAuthResponse().id_token })
.subscribe((data: IUserData) => {
this.userService.set("data.name", "data.email", "data.picture", AuthenticationType.Google);
});
});
});
});
And then it works... So I read about zone and I learnt that it was used to warn angular that it has to run changes detection (or something like that...), but I also read that common functions like setTimeout
or AJAX call are already monkey-patched by "zone", so I don't get how it solves my issue and I don't really get why I have this issue. Why doesn't Angular see that canPostComment
has changed?
As my code is a bit complex, it would be quite hard to plunkr'd it, that's why I copy/paste most of the relevant code.
EDIT
After I asked the question, I had to modify a bit my code because I needed to know when the whole login process was done. Indeed, when the user clicks on the login button, its caption becomes "Logging in..." and once the whole process is completed, it disappears. I could have subscribed to the userService.getStatus
observable, but I decided to go with a promise to be able to do:
this.googleAuthenticationService.login.then(() => { ... });
So I updated the code above this way:
return new Promise((resolve, reject) => {
this.ensureApiIsLoaded().then(() => {
this.auth.signIn().then((user) => {
let profile = user.getBasicProfile();
this.zoneService.run(() => {
this.restService
.post("/api/security/authenticate", <IAuthenticationPayload>{ type: AuthenticationType.Google, token: user.getAuthResponse().id_token })
.subscribe((data: IUserData) => {
this.userService.set(data.name, data.email, data.picture, AuthenticationType.Google);
resolve(true)
},
error => reject(false));
});
});
});
});
Out of curiosity, I tried to remove the this.zoneService.run
statement just to see what happens and it turns out that it works without... So putting everything in a promise seems to fix the issue... However, the question still remains... Why didn't the first example work?