我正在构建一个具有标准RESTful Web服务的网站,用于处理持久性和复杂业务逻辑。我正在使用Angular 2编写组件来构建消费此服务的用户界面。
我希望能够依赖Google网站登录来实现身份验证,而不是构建自己的身份验证系统。这样用户就可以通过提供的框架登录到该网站,然后发送生成的ID令牌,由托管RESTful服务的服务器进行验证。
在Google Sign-In文档中,有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>
并且在Typescript中定义的Angular 2组件:
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);
}
}
基本流程如下:
- Angular 渲染模板并显示消息 "Hello, empty!"。
ngAfterViewInit
钩子被触发,并调用gapi.signin2.render(...)
方法,将空的 div 转换为 Google 登录按钮。这部分功能正常工作,点击该按钮将触发登录过程。- 这也会将组件的
onGoogleLoginSuccess
方法附加到实际处理用户登录后返回的令牌上。 - Angular 检测到
userDisplayName
属性已更改,并更新页面以显示 "Hello, Craig(或您的名字)!"。
首先出现的问题是在 onGoogleLoginSuccess
方法中。请注意 constructor
中和那个方法中的 console.log(...)
调用。如预期的那样,在 constructor
中返回 Angular 组件。然而,在 onGoogleLoginSuccess
方法中返回 JavaScript 的 window
对象。
因此,看起来在跳转到 Google 登录逻辑时丢失了上下文,接下来我尝试使用 jQuery 的 $.proxy
调用来保持正确的上下文。因此,我通过在组件顶部添加 declare var $: any;
来导入 jQuery 命名空间,然后将 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
钩子的末尾添加了以下行:
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) { }
gapi
是从哪里来的?我也在使用你的示例来实现 Google 登录。我声明了变量gapi
,但它是一个空变量,对吧?所以当我尝试调用它的方法时,会出现很多错误。gapi
是从哪里来的?我漏掉了什么? - NDevox