Вход в Google для веб-сайтов и Angular 2 с помощью Typescript

Я создаю сайт с довольно стандартным веб-сервисом RESTful для обработки настойчивости и сложной бизнес-логики. Пользовательский интерфейс, который я создаю для использования этой службы, использует Angular 2 с компонентами, написанными в TypeScript.

Вместо того, чтобы создавать свою собственную систему аутентификации, я надеюсь использовать Google Sign In In для веб-сайтов. Идея заключается в том, что пользователи придут на сайт, войдут в систему через предоставленную там инфраструктуру и затем отправят по итоговым ID-маркерам, которые затем проверит сервер, на котором размещена служба RESTful.

В документации для входа в Google есть инструкции по созданию кнопки входа в систему через JavaScript, что и должно произойти, так как кнопка входа в систему динамически отображается в Angular. Соответствующая часть шаблона:

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

И определение Angular 2 в Typescript:

import {Component} from "angular2/core";

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

Основной поток идет:

  • Angular отображает шаблон и сообщение "Hello, empty!"..
  • Захват ngAfterViewInit запущен и вызывается метод gapi.signin2.render(...), который преобразует пустой div в кнопку входа в Google. Это работает правильно, и нажатие на эту кнопку вызывает процесс входа в систему.
  • Это также добавляет метод компонента onGoogleLoginSuccess для фактической обработки возвращаемого токена после входа пользователя в систему.
  • Angular обнаруживает, что свойство userDisplayName изменилось и обновляет страницу, на которой теперь отображается "Hello, Craig (или как бы то ни было ваше имя)!".

Первая проблема, возникающая в методе onGoogleLoginSuccess. Обратите внимание на вызовы console.log(...) в constructor и в этом методе. Как и ожидалось, один из constructor возвращает компонент Angular. Однако метод onGoogleLoginSuccess возвращает объект JavaScript window.

Таким образом, похоже, что контекст теряется в процессе перехода к логике входа в Google, поэтому моим следующим шагом было попытаться включить вызов jQuery $.proxy, чтобы зависать в правильном контексте. Поэтому я импортирую пространство имен jQuery, добавив declare var $:any; в начало компонента, а затем преобразую содержимое метода ngAfterViewInit в значение:

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

После добавления двух вызовов console.log возвращает один и тот же объект, поэтому значения свойств теперь обновляются правильно. Во втором сообщении журнала отображается объект с ожидаемыми обновленными значениями свойств.

К сожалению, шаблон Angular не обновляется, когда это происходит. Во время отладки я наткнулся на то, что, я считаю, объясняет, что происходит. Я добавил следующую строку в конец ngAfterViewInit hook:

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

Это ничего не должно делать. Он просто ждет пять секунд после того, как крючок заканчивается, а затем устанавливает значение свойства, равное самому себе. Тем не менее, при наличии строки сообщение "Hello, empty!" превращается в "Hello, Craig!" примерно через пять секунд после загрузки страницы. Это подсказывает мне, что Angular просто не замечает, что значения свойств изменяются в методе onGoogleLoginSuccess. Поэтому, когда что-то еще происходит, чтобы уведомить Angular, что значения свойств изменились (например, в противном случае бесполезное самоопределение выше), Angular просыпается и обновляет все.

Очевидно, что не хак, я хочу уйти на место, поэтому мне интересно, могут ли какие-нибудь эксперты Angular найти меня? Есть ли какой-то вызов, который я должен сделать, чтобы заставить Angular заметить, что некоторые свойства изменились?

ОБНОВЛЕНО 2016-02-21, чтобы обеспечить ясность в отношении конкретного ответа, который решил проблему

Мне пришлось использовать обе части предложения, предоставленные в выбранном ответе.

Во-первых, именно так, как было предложено, мне нужно было преобразовать метод onGoogleLoginSuccess для использования функции стрелки. Во-вторых, мне нужно было использовать объект NgZone, чтобы убедиться, что обновления свойств произошли в контексте, о котором известно Angular. Таким образом, окончательный метод выглядел как

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

Мне нужно было импортировать объект _zone: import {Component, NgZone} from "angular2/core";

Мне также нужно было ввести его, как было предложено в ответе, через класс-конструктор: constructor(private _zone: NgZone) { }

Ответ 1

Для вашего первого решения проблемы следует использовать функцию стрелки, которая сохранит контекст this:

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

Вторая проблема возникает, потому что сторонние скрипты выполняются вне контекста Angular. Angular использует zones, поэтому, когда вы запускаете что-то, например setTimeout(), которое обезглавлено для запуска в зоне, Angular получит уведомление. Вы запускаете jQuery в зоне следующим образом:

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

Есть много вопросов/ответов о зоне с гораздо лучшими пояснениями, чем мои, если вы хотите узнать больше, но это не должно быть проблемой для вашего примера, если вы используете функцию стрелки.

Ответ 2

Я сделал компонент google-login, если вам нужен пример.

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

Немного поздно, но я просто хочу привести пример, если кто-то хочет использовать google login api с ng2.

Ответ 3

Включите указанный ниже файл в 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));
      });

 }

Ответ 4

Попробуйте этот пакет - npm install angular2-google-login

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

Я внедрил логин Google в Angular2. Просто импортируйте пакет, и вы готовы к работе.

Шаги -

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

Поставщики услуг - providers: [AuthService];

Конструктор - constructor(private _googleAuth: AuthService){}

Задайте идентификатор клиента Google - AppGlobals.GOOGLE_CLIENT_ID = 'SECRET_CLIENT_ID';

Используйте это для вызова службы -

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

Чтобы выйти из системы -

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

Ответ 5

Выбранный ответ Sasxa также помог мне, но я обнаружил, что могу связать это с функцией onSuccess, используя .bind(this), поэтому мне не нужно создавать функцию с жирной стрелкой.

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