Вход в 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"
});
}