Inicio de sesión de Google para Sitios web y Angular 2 con Typescript


Estoy construyendo un sitio que tiene un servicio web RESTful bastante estándar para manejar la persistencia y la lógica de negocios compleja. La interfaz de usuario que estoy construyendo para consumir este servicio está usando Angular 2 con componentes escritos en TypeScript.

En lugar de construir mi propio sistema de autenticación, espero confiar en el Inicio de sesión de Google para sitios web. La idea es que los usuarios vendrán al sitio, iniciar sesión a través del marco proporcionado allí y luego enviar a lo largo de los tokens de identificación resultantes, que el el servidor que aloja el servicio RESTful puede verificar.

En la documentación de Inicio de sesión de Google hay instrucciones para crear el botón de inicio de sesión a través de JavaScript que es lo que debe suceder ya que el botón de inicio de sesión se representa dinámicamente en una plantilla Angular. La parte pertinente de la plantilla:

<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>

Y la definición de componente Angular 2 en 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);
  }
}

El flujo básico es:

  1. Angular renderiza la plantilla y el mensaje " Hello, empty!"se muestra.
  2. Se activa el gancho ngAfterViewInit y se llama al método gapi.signin2.render(...) que convierte el div vacío en un botón de inicio de sesión de Google. Esto funciona correctamente y al hacer clic en ese botón se activará el proceso de inicio de sesión.
  3. Esto también adjunta el método onGoogleLoginSuccess del componente para procesar realmente el token devuelto después de que un usuario inicie sesión.
  4. Angular detecta que la propiedad userDisplayName ha cambiado y actualiza la página para mostrar ahora " Hola, Craig (o como sea tu nombre is)!".

El primer problema que ocurre está en el método onGoogleLoginSuccess. Observe las llamadas console.log(...) en el constructor y en ese método. Como era de esperar, el de constructor devuelve el componente Angular. El del método onGoogleLoginSuccess, sin embargo, devuelve el objeto JavaScript window.

Así que parece que el contexto se está perdiendo en el proceso de saltar a la lógica de inicio de sesión de Google, así que mi siguiente paso fue intentar incorporar la llamada de jQuery $.proxy para aferrarse al contexto correcto. Así que importe el espacio de nombres jQuery añadiendo declare var $:any; a la parte superior del componente y, a continuación, convierta el contenido del método 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"
      });
}

Después de agregar eso, las dos llamadas console.log devuelven el mismo objeto, por lo que los valores de las propiedades ahora se actualizan correctamente. El segundo mensaje de registro muestra el objeto con los valores de propiedad actualizados esperados.

Desafortunadamente, la plantilla Angular no se actualiza cuando esto sucede. Mientras depuraba, me topé con algo que creo que explica lo que está pasando. He añadido la siguiente línea al final del gancho ngAfterViewInit:

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

Esto realmente no debería hacer nada. Solo espera cinco segundos después de que el gancho termina y luego establece un valor de propiedad igual a sí mismo. Sin embargo, con la línea en su lugar, el mensaje "Hello, empty!" se convierte en "Hello, Craig!" unos cinco segundos después de que la página se haya cargado. Esto me sugiere que Angular simplemente no se está dando cuenta de que los valores de las propiedades están cambiando en el método onGoogleLoginSuccess. Así que cuando algo más sucede a notificar Angular que los valores de propiedad han cambiado (como la auto-asignación de otra manera inútil anterior), Angular se despierta y actualiza todo.

Obviamente no es un truco que quiero dejar en su lugar, así que me pregunto si algún experto Angular puede darme una pista. ¿Hay alguna llamada que deba hacer para forzar a Angular a notar que algunas propiedades han cambiado?

ACTUALIZADO 2016-02-21 para proporcionar claridad sobre la respuesta específica que resolvió el problema

Terminé necesidad de utilizar ambas partes de la sugerencia proporcionada en la respuesta seleccionada.

Primero, exactamente como se sugirió, necesitaba convertir el método onGoogleLoginSuccess para usar una función de flecha. En segundo lugar, necesitaba hacer uso de un objeto NgZone para asegurarme de que las actualizaciones de propiedades ocurrieron en un contexto del cual Angular es consciente. Así que el método final terminó pareciendo

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

Necesitaba importar el objeto _zone: import {Component, NgZone} from "angular2/core";

También tuve que inyectarlo como se sugiere en el respuesta a través del contructor de la clase: constructor(private _zone: NgZone) { }

Author: Craig Phillips, 2016-02-21

4 answers

Para su primera solución del problema es usar la función de flecha que preservará el contexto de this:

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

El segundo problema está ocurriendo porque los scripts de terceros se ejecutan fuera del contexto de Angular. Angular usa zones por lo que cuando ejecuta algo, por ejemplo setTimeout(), que está parcheado para ejecutarse en la zona, Angular recibirá una notificación. Ejecutarías jQuery en la zona así:

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

Hay muchas preguntas / respuestas sobre la zona con explicaciones mucho mejores entonces mío, si quieres saber más, pero no debería ser un problema para tu ejemplo si usas la función de flecha.

 21
Author: Sasxa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-21 01:33:20

Hice un componente de google-login si quieres un ejemplo.

  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);
    });
  }

Es un poco tarde, pero solo quiero dar un ejemplo si alguien quiere usar la api de inicio de sesión de Google con ng2.

 6
Author: Fr4NgUs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-26 19:53:45

Incluya el siguiente archivo en su índice.html

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

Iniciar sesión.html

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

Iniciar sesión.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
Author: Mubashir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-15 06:48:18

Pruebe este paquete - npm install angular2-google-login

[7]} Github - https://github.com/rudrakshpathak/angular2-google-login

He implementado Google login en Angular2. Simplemente importe el paquete y estará listo para comenzar.

Pasos -

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

Proveedores de suministro - providers: [AuthService];

Constructor - constructor(private _googleAuth: AuthService){}

Establecer ID de cliente de Google - AppGlobals.GOOGLE_CLIENT_ID = 'SECRET_CLIENT_ID';

Use esto para llamar al servicio-

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

To cerrar sesión -

this._googleAuth.userLogout(()=>{
  //YOUR_CODE_HERE 
});
 0
Author: Rudraksh Pathak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-12 11:14:07