Why does NgZone solve this problem?

I am creating an application where a user can identify themselves through their google account. Behind the scenes, I am using gapi to handle login. On the other hand, there is an angular service called "user" that has Observable

that passes information to subscribers every time the state (online / offline) of the user changes. Then, to glue it all together, there is a button, and when the user clicks on it, this is what happens:

  • A promise is created and resolved after gapi is initialized. (1st line of code below)
  • When the promise is resolved, the Google login is executed through gapi. (Second line of code below)
  • When the access agreement is resolved, the AJAX request id made to the backend to validate the Google token returns data such as email address, name, etc. and subscribes to this observable. (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 "custom" service that emits the observable value for the user's status (it broadcasts the fact that the user is now authenticated).
  • All subscribers then 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, sometimes the code works and sometimes it doesn't. After some investigation, I noticed that it was related to the duration of my web service execution. To verify this, I made an expression inside it that pauses the request for 2 seconds, in which case my code always fails. But what do I mean by "failure"?

Somewhere on the page, I have a component that subscribes to an observable user service:

this.userService.getStatus().subscribe(status => {
    console.log(status);
    this.canPostComment = (status == UserStatus.Online);
});

      

When the web service is very fast I can see console.log

then the property is canPostComment

updated and that is my opinion too, so no problem. However, when the web service takes a little longer, I can still see console.log

with the correct value, but the view is not refreshed ...

Suspecting that it has something to do with angular change detection, I use this zone like this:

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 the zone and I found out that it was used to warn angular that it should trigger change detection (or something like that ...), but I also read that the general functions like setTimeout

or an AJAX call are already sanitized by the "zone" so I don't understand how it solves my problem, and I really don't understand why I have this problem. Why doesn't angular see what canPostComment

has changed?

Since my code is a bit complex it would be quite difficult to do this, so I copy / paste most of the relevant code.

EDIT

After I asked the question, I had to change my code a bit because I needed to know when the entire login process was completed. Indeed, when the user clicks the login button, the title becomes "Logging in ..." and once the whole process is complete, it will disappear. I could subscribe to an observable userService.getStatus

, but I decided to go with a promise to be able to:

this.googleAuthenticationService.login.then(() => { ... });

      

So, I have updated the code above this path:

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 operator this.zoneService.run

just to find out what would happen and it turns out that it works without ... So everything that appears in the promise fixes the problem ... However, the question still remains ... Why first example didn't work?

+3


source to share





All Articles