Pregunta Inicio de sesión en Google para sitios web y Angular 2 usando Typescript


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

En lugar de crear 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 accedan al sitio, inicien sesión a través del marco provisto allí y luego envíen los tokens de ID resultantes, que 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 relevante 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 del 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 va:

  1. Angular representa la plantilla y el mensaje "¡Hola, vacío!" se muestra.
  2. los ngAfterViewInit gancho se dispara y el gapi.signin2.render(...) se llama método 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 une el componente onGoogleLoginSuccess método para procesar realmente el token devuelto después de que un usuario inicia sesión
  4. Angular detecta que el userDisplayName la propiedad ha cambiado y actualiza la página para mostrar ahora "¡Hola, Craig (o como se llame)!".

El primer problema que ocurre es en el onGoogleLoginSuccess método. Observe la console.log(...) llama en el constructor y en ese método. Como se esperaba, el que está en el constructor devuelve el componente angular. El de la onGoogleLoginSuccess método, sin embargo, devuelve el JavaScript window objeto.

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 jQuery's $.proxy llamar para aferrarse al contexto correcto. Así que importo el espacio de nombres jQuery añadiendo declare var $:any; a la parte superior del componente y luego convertir el contenido de la ngAfterViewInit método para ser:

// 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, los dos console.log las llamadas devuelven el mismo objeto, por lo que los valores de propiedad ahora se actualizan correctamente. El segundo mensaje de registro muestra el objeto con los valores de propiedad actualizados esperados.

Lamentablemente, la plantilla angular no se actualiza cuando sucede esto. Mientras estaba depurando, tropecé con algo que creo que explica lo que está sucediendo. Agregué la siguiente línea al final de la ngAfterViewInit gancho:

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

Esto no debería hacer nada. Solo espera cinco segundos después de que el gancho finaliza y luego establece un valor de propiedad igual a él. Sin embargo, con la línea en su lugar, "Hello, empty!" el mensaje 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 está notando que los valores de las propiedades están cambiando en el onGoogleLoginSuccess método. Entonces, cuando sucede algo más que notifica a Angular que los valores de las propiedades han cambiado (como la autoasignación inútil anterior), Angular se despierta y actualiza todo.

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

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

Terminé necesitando usar ambas piezas de la sugerencia proporcionada en la respuesta seleccionada.

Primero, exactamente como se sugirió, necesitaba convertir el onGoogleLoginSuccess método para usar una función de flecha. En segundo lugar, necesitaba hacer uso de un NgZone Objeto para asegurarse de que las actualizaciones de propiedad se produjeron en un contexto del que Angular tiene conocimiento. Entonces el método final terminó luciendo como

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

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

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


32
2018-02-20 23:42


origen


Respuestas:


Para su primera solución problema es usar 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á sucediendo porque los scripts de terceros se ejecutan fuera del contexto de Angular. Usos angulares zones así que cuando ejecutas algo, por ejemplo setTimeout(), que tiene parche de mono para correr en la zona, Angular recibirá una notificación. Deberías ejecutar jQuery en una zona como esta:

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

Hay muchas preguntas / respuestas sobre la zona con explicaciones mucho mejores que las mías, si quiere saber más, pero no debería ser un problema para su ejemplo si utiliza la función de flecha.


21
2018-02-21 01:33



Hice un componente de inicio de sesión de google 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 Google api con ng2.


6
2018-04-01 08:20



Incluya el archivo a continuación en su 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
2018-03-15 06:48



Prueba este paquete - npm install angular2-google-login

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

Implementé el inicio de sesión de Google en Angular2. Solo importa el paquete y ya estás listo para comenzar.

Pasos -

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

Proveedores de suministros 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 
});

Para cerrar la sesión -

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

0
2018-05-12 11:14