webentwicklung-frage-antwort-db.com.de

Google Sign-In für Websites und Angular 2 using Typescript

Ich erstelle eine Site, die über einen ziemlich standardmäßigen RESTful-Webdienst verfügt, um mit Persistenz und komplexer Geschäftslogik umzugehen. Die Benutzeroberfläche, die ich für diesen Dienst erstelle, verwendet Angular 2 für in TypeScript geschriebene Komponenten.

Anstatt mein eigenes Authentifizierungssystem aufzubauen, hoffe ich, dass ich mich auf Google Sign-In für Websites verlassen kann. Die Idee ist, dass Benutzer die Site besuchen, sich über das dort bereitgestellte Framework anmelden und dann die resultierenden ID-Token senden, die der Server, der den RESTful-Service hostet, dann überprüfen kann.

In der Google-Anmeldedokumentation gibt es Anweisungen zum Erstellen der Anmeldeschaltfläche über JavaScript Dies muss geschehen, da die Anmeldeschaltfläche in einer Angular-Vorlage dynamisch gerendert wird. Der relevante Teil der Vorlage:

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

Und die Angular 2-Komponentendefinition 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);
  }
}

Der grundlegende Ablauf lautet:

  1. Angular rendert die Vorlage und die Meldung "Hallo, leer!" wird gezeigt.
  2. Der Haken ngAfterViewInit wird ausgelöst und die Methode gapi.signin2.render(...) aufgerufen, die das leere div in eine Google-Anmeldeschaltfläche konvertiert. Dies funktioniert ordnungsgemäß und ein Klick auf diese Schaltfläche löst den Anmeldevorgang aus.
  3. Hiermit wird auch die onGoogleLoginSuccess -Methode der Komponente angehängt, um das zurückgegebene Token tatsächlich zu verarbeiten, nachdem sich ein Benutzer angemeldet hat.
  4. Angular erkennt, dass sich die userDisplayName -Eigenschaft geändert hat, und aktualisiert die Seite so, dass nun "Hallo, Craig (oder wie auch immer Ihr Name lautet)!" Angezeigt wird.

Das erste Problem, das auftritt, befindet sich in der onGoogleLoginSuccess Methode. Beachten Sie die console.log(...) -Aufrufe in constructor und in dieser Methode. Wie erwartet gibt die in constructor angegebene Komponente die Komponente Angular zurück. Die Methode in onGoogleLoginSuccess gibt jedoch das JavaScript-Objekt window zurück.

Es sieht also so aus, als würde der Kontext beim Wechseln zur Anmeldelogik von Google verloren gehen. Deshalb bestand mein nächster Schritt darin, den $.proxy - Aufruf von jQuery zu verwenden, um den korrekten Kontext beizubehalten. Daher importiere ich den jQuery-Namespace, indem ich declare var $:any; Am Anfang der Komponente hinzufüge und dann den Inhalt der ngAfterViewInit -Methode wie folgt konvertiere:

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

Nachdem Sie dies hinzugefügt haben, geben die beiden Aufrufe console.log Dasselbe Objekt zurück, sodass die Eigenschaftswerte jetzt korrekt aktualisiert werden. Die zweite Protokollmeldung zeigt das Objekt mit den erwarteten aktualisierten Eigenschaftswerten.

Leider wird die Vorlage Angular in diesem Fall nicht aktualisiert. Während des Debuggens bin ich auf etwas gestoßen, von dem ich glaube, dass es erklärt, was los ist. Ich habe die folgende Zeile am Ende des Hooks ngAfterViewInit eingefügt:

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

Das sollte eigentlich nichts bringen. Es wartet nur fünf Sekunden nach dem Ende des Hooks und setzt dann einen Eigenschaftswert auf sich selbst. Wenn jedoch die Zeile vorhanden ist, wird die Meldung "Hello, empty!" Etwa fünf Sekunden nach dem Laden der Seite zu "Hello, Craig!". Dies legt den Schluss nahe, dass Angular einfach nicht bemerkt, dass sich die Eigenschaftswerte in der onGoogleLoginSuccess -Methode ändern. Wenn also etwas anderes passiert, um Angular zu benachrichtigen, dass Eigenschaftswerte geändert wurden (z. B. die ansonsten nutzlose Selbstzuweisung oben), wird Angular aktiviert und aktualisiert alles.

Offensichtlich ist das kein Hack, den ich an Ort und Stelle lassen möchte. Ich frage mich, ob mich irgendjemand da draußen, der Angular Experte kennt, darauf hinweisen kann. Gibt es einen Anruf, den ich machen sollte, um Angular zu zwingen, um festzustellen, dass sich einige Eigenschaften geändert haben?

AKTUALISIERT am 21.02.2016, um Klarheit über die spezifische Antwort zu schaffen, die das Problem gelöst hat

Am Ende musste ich beide Teile des Vorschlags in der ausgewählten Antwort verwenden.

Zuerst musste ich genau wie vorgeschlagen die onGoogleLoginSuccess -Methode konvertieren, um eine Pfeilfunktion zu verwenden. Zweitens musste ich ein NgZone -Objekt verwenden, um sicherzustellen, dass die Eigenschaftsaktualisierungen in einem Kontext erfolgten, von dem Angular Kenntnis hat. Die endgültige Methode sah also so aus

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

Ich musste das Objekt _zone Importieren: import {Component, NgZone} from "angular2/core";

Ich musste es auch wie in der Antwort vorgeschlagen über den Konstruktor der Klasse injizieren: constructor(private _zone: NgZone) { }

35
Craig Phillips

Als erste Problemlösung verwenden Sie Pfeilfunktion , wodurch der Kontext von this erhalten bleibt:

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

Das zweite Problem tritt auf, weil Skripts von Drittanbietern außerhalb des Angular-Kontexts ausgeführt werden. Angular verwendet zones. Wenn Sie also etwas ausführen, zum Beispiel setTimeout(), das mit einem Affen-Patch für die Ausführung in der Zone versehen ist, wird Angular abgerufen benachrichtigt. Sie würden jQuery in der folgenden Zone ausführen:

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

Es gibt viele Fragen/Antworten zu der Zone mit viel besseren Erklärungen als meiner, wenn Sie mehr wissen möchten, aber es sollte kein Problem für Ihr Beispiel sein, wenn Sie die Pfeilfunktion verwenden.

22
Sasxa

Ich habe eine Google-Login-Komponente erstellt, wenn Sie ein Beispiel wünschen.

  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 ist ein bisschen spät, aber ich möchte nur ein Beispiel geben, wenn jemand Google Login API mit ng2 verwenden möchte.

8
Fr4NgUs

Fügen Sie die folgende Datei in Ihre index.html ein

<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

Probieren Sie dieses Paket aus - npm install angular2-google-login

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

Ich habe die Google-Anmeldung in Angular2 implementiert. Importieren Sie einfach das Paket und Sie können loslegen.

Schritte -

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

Versorgungsanbieter -providers: [AuthService];

Konstruktor -constructor(private _googleAuth: AuthService){}

Google-Kunden-ID festlegen -AppGlobals.GOOGLE_CLIENT_ID = 'SECRET_CLIENT_ID';

Hiermit rufen Sie den Service auf -

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

Abmelden -

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

Die ausgewählte Antwort von Sasxa hat mir auch geholfen, aber ich habe festgestellt, dass ich dies mit . Bind (this) an die Funktion onSuccess binden kann, sodass ich die Funktion nicht mit einem fetten Pfeil erstellen muss.

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