Accesso Google per siti Web e Angular 2 utilizzando Typescript

Sto costruendo un sito che ha un servizio web RESTful piuttosto standardizzato per gestire la persistenza e la complessa logica di business. L’interfaccia utente che sto sviluppando per utilizzare questo servizio utilizza Angular 2 con componenti scritti in TypeScript.

Piuttosto che build il mio sistema di autenticazione, spero di fare affidamento su Google Sign-in per i siti web. L’idea è che gli utenti arrivino al sito, accedano tramite il framework fornito e quindi inviino i token ID risultanti, che il server che ospita il servizio RESTful può quindi verificare.

Nella documentazione di accesso a Google ci sono le istruzioni per creare il pulsante di accesso tramite JavaScript, che è ciò che deve accadere poiché il pulsante di accesso viene reso dynamicmente in un modello angular. La parte rilevante del modello:

  

E la definizione del componente Angular 2 in 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); } } 

Il stream di base va:

  1. Angular esegue il rendering del template e il messaggio “Hello, empty!” è mostrato.
  2. L’hook ngAfterViewInit viene ngAfterViewInit e viene chiamato il metodo gapi.signin2.render(...) che converte il div vuoto in un pulsante di accesso Google. Funziona correttamente e facendo clic su quel pulsante si attiverà la procedura di accesso.
  3. Questo attacca anche il metodo onGoogleLoginSuccess del componente per elaborare effettivamente il token restituito dopo che un utente ha effettuato l’accesso.
  4. Angular rileva che la proprietà userDisplayName è cambiata e aggiorna la pagina per visualizzare ora “Ciao, Craig (o come ti chiami)!”.

Il primo problema che si verifica è nel metodo onGoogleLoginSuccess . Notare le chiamate console.log(...) nel constructor e in quel metodo. Come previsto, quello nel constructor restituisce il componente angular. Quello nel metodo onGoogleLoginSuccess , tuttavia, restituisce l’object window JavaScript.

Quindi sembra che il contesto si stia perdendo nel processo di saltare alla logica di login di Google, quindi il mio prossimo passo è stato provare ad incorporare la chiamata $.proxy jQuery per aggrapparmi al contesto corretto. Quindi importare lo spazio dei nomi jQuery aggiungendo declare var $:any; all’inizio del componente e quindi convertire i contenuti del metodo ngAfterViewInit modo che siano:

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

Dopo averlo aggiunto, le due chiamate console.log restituiscono lo stesso object, pertanto i valori delle proprietà vengono ora aggiornati correttamente. Il secondo messaggio di log mostra l’object con i valori di proprietà aggiornati attesi.

Sfortunatamente, il modello Angolare non viene aggiornato quando ciò accade. Durante il debug, sono incappato in qualcosa che, a mio avviso, spiega cosa sta succedendo. Ho aggiunto la seguente riga alla fine del hook ngAfterViewInit :

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

Questo non dovrebbe fare nulla. Attende solo cinque secondi dopo che l’hook termina e quindi imposta un valore di proprietà uguale a se stesso. Tuttavia, con la riga in posizione "Hello, empty!" il messaggio diventa "Hello, Craig!" circa cinque secondi dopo che la pagina è stata caricata. Questo mi suggerisce che Angular semplicemente non sta notando che i valori delle proprietà stanno cambiando nel metodo onGoogleLoginSuccess . Quindi, quando accade qualcos’altro che notifica ad Angular che i valori delle proprietà sono cambiati (come l’autoassegnazione altrimenti inutile di cui sopra), Angular si sveglia e aggiorna tutto.

Ovviamente non è un trucco che voglio lasciare sul posto, quindi mi chiedo se qualche esperto angular là fuori possa indurmi a entrare? C’è qualche chiamata che dovrei fare per forzare Angular a notare che alcune proprietà sono cambiate?

AGGIORNATO 2016-02-21 per fornire chiarezza sulla risposta specifica che ha risolto il problema

Alla fine ho dovuto usare entrambi i pezzi del suggerimento forniti nella risposta selezionata.

Innanzitutto, esattamente come suggerito, avevo bisogno di convertire il metodo onGoogleLoginSuccess per utilizzare una funzione freccia. In secondo luogo, avevo bisogno di utilizzare un object NgZone per assicurarmi che gli aggiornamenti delle proprietà si NgZone verificati in un contesto di cui Angular è a conoscenza. Quindi il metodo finale ha finito per assomigliare

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

Ho dovuto importare l’object _zone : import {Component, NgZone} from "angular2/core";

Avevo anche bisogno di iniettarlo come suggerito nella risposta tramite il contructor della class: constructor(private _zone: NgZone) { }

Per il tuo primo problema la soluzione è usare la funzione freccia che manterrà il contesto di this :

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

Il secondo problema sta accadendo perché gli script di terze parti vengono eseguiti al di fuori del contesto di Angular. Angular usa le zones così quando esegui qualcosa, ad esempio setTimeout() , che è patchato nella scimmia per essere eseguito nella zona, Angular riceverà una notifica. Dovresti eseguire jQuery in una zona come questa:

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

Ci sono molte domande / risposte sulla zona con spiegazioni molto migliori della mia, se vuoi saperne di più, ma non dovrebbe essere un problema per il tuo esempio se usi la funzione freccia.

Ho creato un componente di google-login se vuoi un esempio.

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

È un po ‘tardi ma voglio solo dare un esempio se qualcuno vuole usare google login API con ng2.

Includi il file sottostante nel tuo index.html

  

login.html

  

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

Prova questo pacchetto – npm install angular2-google-login

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

Ho implementato l’accesso a Google in Angular2. Importa il pacchetto e sei pronto per partire.

Passi –

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

Fornitori di forniture – providers: [AuthService];

Costruttore – constructor(private _googleAuth: AuthService){}

Imposta l’ID client Google – AppGlobals.GOOGLE_CLIENT_ID = 'SECRET_CLIENT_ID';

Usa questo per chiamare il servizio –

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

Per disconnettersi –

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