我正在构建一个具有相当标准的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);
  }
}

基本流程如下:

  1. Angular渲染模板和消息"Hello,empty!"如图所示.
  2. 启动ngAfterViewInit钩子,调用gapi.signin2.render(...)方法,将空div转换为Google登录按钮.这可以正常工作,点击该按钮将触发登录过程.
  3. 这还附加了组件的onGoogleLoginSuccess方法,以便在用户登录后实际处理返回的令牌.
  4. 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) { }

推荐答案

第一个问题的解决方案是使用arrow function,这将保留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);
    });
  }

如果你想知道更多,关于这个区域有很多问题/答案,解释比我的好得多,但是如果你使用箭头功能,这个问题不应该成为你的例子的问题.

Typescript相关问答推荐

React Hook中基于动态收件箱定义和断言回报类型

类型安全JSON解析

泛型函数类型验证

接口的额外属性作为同一接口的方法的参数类型'

使用`renderToPipeableStream`时如何正确恢复服务器端Apollo缓存?

如何在TypeScrip中从字符串联合类型中省略%1值

使用打字Angular 中的通用数据创建状态管理

了解类型规则中的关键字

被推断为原始类型的交集的扩充类型的交集

等待用户在Angular 函数中输入

从对象类型描述生成类型

如何通过TypeScrip中的函数防止映射类型中未能通过复杂推理的any值

将具有Readonly数组属性的对象接口转换为数组

Angular 自定义验证控件未获得表单值

返回嵌套props 的可变元组

如何解决&Quot;类型不可分配给TypeScrip IF语句中的类型&Quot;?

如何使用TypeScrip在EXPO路由链路组件中使用动态路由?

Typescript .根据枚举输入参数返回不同的对象类型

将类型脚本函数返回类型提取到VSCode中的接口

带有';的类型脚本嵌套对象可选属性错误满足';