javascript - español - Inicio de sesión en Google para sitios web y Angular 2 usando Typescript
nativescript angular (4)
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 creando para consumir este servicio utiliza 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:
- Angular representa la plantilla y el mensaje "¡Hola, vacío!" se muestra.
- El gancho
ngAfterViewInit
sengAfterViewInit
y se llama al métodogapi.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. - Esto también
onGoogleLoginSuccess
métodoonGoogleLoginSuccess
del componente para procesar realmente el token devuelto después de que un usuario inicia sesión. - Angular detecta que la propiedad
userDisplayName
ha cambiado y actualiza la página para mostrar ahora "¡Hola, Craig (o como seuserDisplayName
)!".
El primer problema que ocurre es en el método onGoogleLoginSuccess
. Observe que console.log(...)
llama al constructor
y a ese método. Como se esperaba, el que está en el constructor
devuelve el componente angular. El que está en el método onGoogleLoginSuccess
, sin embargo, devuelve el objeto de la window
JavaScript.
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 próximo paso fue intentar incorporar la llamada $.proxy
jQuery para $.proxy
al contexto correcto. Así que importo el espacio de nombres jQuery agregando declare var $:any;
en la parte superior del componente y luego 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 a console.log
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. ngAfterViewInit
la siguiente línea al final del ngAfterViewInit
:
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 método onGoogleLoginSuccess
. 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 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 propiedad ocurrieran en un contexto que Angular conoce. Entonces el método final terminó luciendo como
onGoogleLoginSuccess = (loggedInUser) => {
this._zone.run(() => {
this.userAuthToken = loggedInUser.getAuthResponse().id_token;
this.userDisplayName = loggedInUser.getBasicProfile().getName();
});
}
_zone
importar el objeto _zone
: 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) { }
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.
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));
});
}
Para su primera solución problema es utilizar la función de flecha que conservará 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. Angular usa zones
modo que cuando ejecuta algo, por ejemplo setTimeout()
, que tiene parche de mono para ejecutarse en la zona, se notificará a Angular. 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.
Pruebe 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
});