web-dev-qa-db-fra.com

Connexion à Google pour les sites Web et Angular 2 à l'aide de Typescript

Je crée un site qui dispose d'un service Web RESTful assez standard pour gérer la persistance et la logique métier complexe. L'interface utilisateur que je construis pour utiliser ce service utilise Angular 2 avec des composants écrits en TypeScript.

Plutôt que de créer mon propre système d'authentification, j'espère pouvoir compter sur la connexion à Google pour les sites Web. L'idée étant que les utilisateurs viennent sur le site, se connectent via le cadre qui y est fourni et envoient ensuite les jetons d'identification résultants, que le serveur hébergeant le service RESTful peut ensuite vérifier.

Dans la documentation de connexion à Google, il y a instructions pour créer le bouton de connexion via JavaScript ce qui doit se produire puisque le bouton de connexion est rendu dynamiquement dans un Angular La partie pertinente du modèle:

<div class="login-wrapper">
  <p>You need to log in.</p>
  <div id="{{googleLoginButtonId}}"></div>
</div>
<div class="main-application">
  <p>Hello, {{userDisplayName}}!</p>
</div>

Et la définition du composant Angular 2 dans TypeScript:

import {Component} from "angular2/core";

// Google's login API namespace
declare var gapi:any;

@Component({
    selector: "sous-app",
    templateUrl: "templates/sous-app-template.html"
})
export class SousAppComponent {
  googleLoginButtonId = "google-login-button";
  userAuthToken = null;
  userDisplayName = "empty";

  constructor() {
    console.log(this);
  }

  // Angular hook that allows for interaction with elements inserted by the
  // rendering of a view.
  ngAfterViewInit() {
    // Converts the Google login button stub to an actual button.
    api.signin2.render(
      this.googleLoginButtonId,
      {
        "onSuccess": this.onGoogleLoginSuccess,
        "scope": "profile",
        "theme": "dark"
      });
  }

  // Triggered after a user successfully logs in using the Google external
  // login provider.
  onGoogleLoginSuccess(loggedInUser) {
    this.userAuthToken = loggedInUser.getAuthResponse().id_token;
    this.userDisplayName = loggedInUser.getBasicProfile().getName();
    console.log(this);
  }
}

Le flux de base va:

  1. Angular rend le modèle et le message "Bonjour, vide!" est montré.
  2. Le hook ngAfterViewInit est déclenché et la méthode gapi.signin2.render(...) est appelée, ce qui convertit le div vide en bouton de connexion Google. Cela fonctionne correctement et cliquer sur ce bouton déclenchera le processus de connexion.
  3. Cela attache également la méthode onGoogleLoginSuccess du composant pour réellement traiter le jeton renvoyé après la connexion d'un utilisateur.
  4. Angular détecte que la propriété userDisplayName a changé et met à jour la page pour afficher maintenant "Bonjour, Craig (ou quel que soit votre nom)!".

Le premier problème qui se produit est dans la méthode onGoogleLoginSuccess. Remarquez les appels console.log(...) dans le constructor et dans cette méthode. Comme prévu, celui de constructor renvoie le Angular. Cependant, celui de la méthode onGoogleLoginSuccess renvoie le JavaScript window objet.

Il semble donc que le contexte se perd dans le processus de passage à la logique de connexion de Google, donc ma prochaine étape a été d'essayer d'incorporer l'appel de jQuery $.proxy Pour conserver le bon contexte. J'importe donc l'espace de noms jQuery en ajoutant declare var $:any; En haut du composant, puis convertis le contenu de la méthode ngAfterViewInit en:

// Angular hook that allows for interaction with elements inserted by the
// rendering of a view.
ngAfterViewInit() {
    var loginProxy = $.proxy(this.onGoogleLoginSuccess, this);

    // Converts the Google login button stub to an actual button.
    gapi.signin2.render(
      this.googleLoginButtonId,
      {
        "onSuccess": loginProxy,
        "scope": "profile",
        "theme": "dark"
      });
}

Après avoir ajouté cela, les deux appels console.log Renvoient le même objet afin que les valeurs des propriétés se mettent à jour correctement. Le deuxième message de journal affiche l'objet avec les valeurs de propriété mises à jour attendues.

Malheureusement, le modèle Angular n'est pas mis à jour lorsque cela se produit. Pendant le débogage, je suis tombé sur quelque chose qui, je crois, explique ce qui se passe. J'ai ajouté la ligne suivante à la fin du ngAfterViewInit hook:

setTimeout(function() {
  this.googleLoginButtonId = this.googleLoginButtonId },
  5000);

Cela ne devrait vraiment rien faire. Il attend juste cinq secondes après la fin du hook, puis définit une valeur de propriété égale à elle-même. Cependant, avec la ligne en place, le message "Hello, empty!" Se transforme en "Hello, Craig!" Environ cinq secondes après le chargement de la page. Cela me suggère que Angular ne remarque tout simplement pas que les valeurs des propriétés changent dans la méthode onGoogleLoginSuccess. Donc, quand quelque chose d'autre arrive à notifier Angular que les valeurs des propriétés ont changé (comme l'auto-affectation autrement inutile ci-dessus), Angular se réveille et met à jour tout.

Évidemment, ce n'est pas un hack que je veux laisser en place, donc je me demande si des experts Angular là-bas peuvent me donner une idée? Y a-t-il un appel que je devrais faire pour forcer Angular pour remarquer que certaines propriétés ont changé?

MISE À JOUR 2016-02-21 pour clarifier la réponse spécifique qui a résolu le problème

J'ai fini par avoir besoin d'utiliser les deux morceaux de la suggestion fournie dans la réponse sélectionnée.

Tout d'abord, exactement comme suggéré, j'avais besoin de convertir la méthode onGoogleLoginSuccess pour utiliser une fonction de flèche. Deuxièmement, je devais utiliser un objet NgZone pour m'assurer que les mises à jour de propriétés se sont produites dans un contexte dont Angular est au courant. Ainsi, la méthode finale a fini par ressembler à

onGoogleLoginSuccess = (loggedInUser) => {
    this._zone.run(() => {
        this.userAuthToken = loggedInUser.getAuthResponse().id_token;
        this.userDisplayName = loggedInUser.getBasicProfile().getName();
    });
}

J'ai eu besoin d'importer l'objet _zone: import {Component, NgZone} from "angular2/core";

J'ai également dû l'injecter comme suggéré dans la réponse via le constructeur de la classe: constructor(private _zone: NgZone) { }

35
Craig Phillips

Pour votre premier problème, la solution consiste à utiliser fonction flèche qui préservera le contexte de this:

  onGoogleLoginSuccess = (loggedInUser) => {
    this.userAuthToken = loggedInUser.getAuthResponse().id_token;
    this.userDisplayName = loggedInUser.getBasicProfile().getName();
    console.log(this);
  }

Le deuxième problème se produit car les scripts tiers s'exécutent en dehors du contexte d'Angular. Angular utilise zones donc lorsque vous exécutez quelque chose, par exemple setTimeout(), qui est patché par des singes pour s'exécuter dans la zone, Angular sera notifié. Vous exécuterez jQuery dans une zone comme celle-ci:

  constructor(private zone: NgZone) {
    this.zone.run(() => {
      $.proxy(this.onGoogleLoginSuccess, this);
    });
  }

Il y a beaucoup de questions/réponses sur la zone avec de bien meilleures explications que la mienne, si vous voulez en savoir plus, mais cela ne devrait pas être un problème pour votre exemple si vous utilisez la fonction flèche.

22
Sasxa

J'ai créé un composant google-login si vous voulez un exemple.

  ngOnInit()
  {
    this.initAPI = new Promise(
        (resolve) => {
          window['onLoadGoogleAPI'] =
              () => {
                  resolve(window.gapi);
          };
          this.init();
        }
    )
  }

  init(){
    let meta = document.createElement('meta');
    meta.name = 'google-signin-client_id';
    meta.content = 'xxxxx-xxxxxx.apps.googleusercontent.com';
    document.getElementsByTagName('head')[0].appendChild(meta);
    let node = document.createElement('script');
    node.src = 'https://apis.google.com/js/platform.js?onload=onLoadGoogleAPI';
    node.type = 'text/javascript';
    document.getElementsByTagName('body')[0].appendChild(node);
  }

  ngAfterViewInit() {
    this.initAPI.then(
      (gapi) => {
        gapi.load('auth2', () =>
        {
          var auth2 = gapi.auth2.init({
            client_id: 'xxxxx-xxxxxx.apps.googleusercontent.com',
            cookiepolicy: 'single_Host_Origin',
            scope: 'profile email'
          });
          auth2.attachClickHandler(document.getElementById('googleSignInButton'), {},
              this.onSuccess,
              this.onFailure
          );
        });
      }
    )
  }

  onSuccess = (user) => {
      this._ngZone.run(
          () => {
              if(user.getAuthResponse().scope ) {
                  //Store the token in the db
                  this.socialService.googleLogIn(user.getAuthResponse().id_token)
              } else {
                this.loadingService.displayLoadingSpinner(false);
              }
          }
      );
  };

  onFailure = (error) => {
    this.loadingService.displayLoadingSpinner(false);
    this.messageService.setDisplayAlert("error", error);
    this._ngZone.run(() => {
        //display spinner
        this.loadingService.displayLoadingSpinner(false);
    });
  }

Il est un peu tard mais je veux juste donner un exemple si quelqu'un veut utiliser l'api de connexion google avec ng2.

8
Fr4NgUs

Incluez le fichier ci-dessous dans votre index.html

<script src="https://apis.google.com/js/platform.js" async defer></script>

login.html

<button id="glogin">google login</button>

login.ts

declare const gapi: any;
public auth2:any
ngAfterViewInit() {
     gapi.load('auth2',  () => {
      this.auth2 = gapi.auth2.init({
        client_id: '788548936361-h264uq1v36c5ddj0hf5fpmh7obks94vh.apps.googleusercontent.com',
        cookiepolicy: 'single_Host_Origin',
        scope: 'profile email'
      });
      this.attachSignin(document.getElementById('glogin'));
    });
}

public attachSignin(element) {
    this.auth2.attachClickHandler(element, {},
      (loggedInUser) => {  
      console.log( loggedInUser);

      }, function (error) {
        // alert(JSON.stringify(error, undefined, 2));
      });

 }
3
Mubashir

Essayez ce package - npm install angular2-google-login

Github - https://github.com/rudrakshpathak/angular2-google-login

J'ai implémenté la connexion Google dans Angular2. Importez simplement le package et vous êtes prêt à partir.

Étapes -

import { AuthService, AppGlobals } from 'angular2-google-login';

Fournisseurs de fournitures -providers: [AuthService];

Constructeur -constructor(private _googleAuth: AuthService){}

Définir l'ID client Google -AppGlobals.GOOGLE_CLIENT_ID = 'SECRET_CLIENT_ID';

Utilisez ceci pour appeler le service -

this._googleAuth.authenticateUser(()=>{
  //YOUR_CODE_HERE 
});

Pour vous déconnecter -

this._googleAuth.userLogout(()=>{
  //YOUR_CODE_HERE 
});
1
Rudraksh Pathak

La réponse choisie par Sasxa m'a également aidé mais j'ai trouvé que je pouvais le lier à la fonction onSuccess en utilisant . Bind (this), de cette façon, je n'ai pas à créer la fonction avec une grosse flèche.

ngAfterViewInit() {
  var loginProxy = $.proxy(this.onGoogleLoginSuccess, this);

  // Converts the Google login button stub to an actual button.
  gapi.signin2.render(
    this.googleLoginButtonId,
    {
      "onSuccess": loginProxy.bind(this),
      "scope": "profile",
      "theme": "dark"
    });
}
0
Simon245