我正在构建一个具有相当标准的RESTful web服务的网站,以处理持久性和复杂的业务逻辑.我为使用这项服务而构建的用户界面使用了Angular 2个用TypeScript编写的组件.
我希望依靠谷歌登录网站,而不是建立自己的认证系统.其 idea 是,用户将来到该站点,通过那里提供的框架登录,然后发送生成的ID令牌,托管RESTful服务的服务器随后可以验证这些令牌.
在谷歌登录文档中,有instructions for creating the login button via 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
对象.
因此,在跳转到谷歌的登录逻辑的过程中,上下文似乎丢失了,所以我的下一步是try 合并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
调用返回相同的对象,因此属性值现在正在正确更新.第二条日志(log)消息显示具有预期更新属性值的对象.
不幸的是,发生这种情况时,Angular 模板不会得到更新.在调试过程中,我偶然发现了一些我认为可以解释发生了什么的东西.我在ngAfterViewInit
号弯钩的末端添加了以下线条:
setTimeout(function() {
this.googleLoginButtonId = this.googleLoginButtonId },
5000);
这真的没什么用.它只需在钩子结束后等待五秒钟,然后将属性值设置为等于自身.然而,在页面加载大约五秒钟后,当该行就位时,"Hello, empty!"
条消息变成"Hello, Craig!"
条.这对我来说意味着Angular没有注意到onGoogleLoginSuccess
方法中的属性值正在改变.因此,当发生其他事情通知Angular属性值已更改时(例如上面无用的自赋值),Angular会唤醒并更新所有内容.
很明显,这不是我想留下来的黑客,所以我想知道是否有任何Angular 专家可以给我提供线索?我应该打电话让Angular注意到一些属性已经改变了吗?
UPDATED 2016-02-21 to provided clarity on the specific answer that solved the problem
最后,我需要使用所选答案中提供的两条建议.
首先,正如建议的那样,我需要将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) { }