谷歌登录网站和Angular 2使用Typescript

Cra*_*ips 35 javascript google-login typescript angular

我正在构建一个具有非常标准的RESTful Web服务的站点来处理持久性和复杂的业务逻辑.我正在构建以使用此服务的UI使用Angular 2和使用TypeScript编写的组件.

我希望依靠谷歌登录网站,而不是建立我自己的身份验证系统.想法是用户将访问该站点,通过其中提供的框架登录,然后发送生成的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>
Run Code Online (Sandbox Code Playgroud)

和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);
  }
}
Run Code Online (Sandbox Code Playgroud)

基本流程如下:

  1. Angular呈现模板和消息"Hello,empty!" 显示.
  2. ngAfterViewInit钩子被触发并gapi.signin2.render(...)调用该方法,该方法将空div转换为Google登录按钮.这可以正常工作,单击该按钮将触发登录过程.
  3. 这还会附加组件的onGoogleLoginSuccess方法,以便在用户登录后实际处理返回的令牌.
  4. Angular检测到userDisplayName属性已更改并更新页面现在显示"Hello,Craig(或您的名字)!".

发生的第一个问题是在onGoogleLoginSuccess方法中.注意在该方法中的console.log(...)调用constructor.正如预期的那样,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"
      });
}
Run Code Online (Sandbox Code Playgroud)

添加之后,两个console.log调用将返回相同的对象,因此属性值现在可以正确更新.第二条日志消息显示具有预期更新属性值的对象.

不幸的是,当发生这种情况时,Angular模板不会更新.在调试时,我偶然发现了一些我认为可以解释发生了什么的事情.我在ngAfterViewInit钩子的末尾添加了以下行:

setTimeout(function() {
  this.googleLoginButtonId = this.googleLoginButtonId },
  5000);
Run Code Online (Sandbox Code Playgroud)

这不应该做任何事情.它只是在钩子结束后等待五秒钟,然后设置一个等于它自己的属性值.但是,在页面加载后,"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();
    });
}
Run Code Online (Sandbox Code Playgroud)

我确实需要导入_zone对象:import {Component, NgZone} from "angular2/core";

我还需要通过类的构造函数按照答案中的建议注入它: constructor(private _zone: NgZone) { }

Sas*_*sxa 22

对于您的第一个问题解决方案是使用箭头函数,它将保留上下文this:

  onGoogleLoginSuccess = (loggedInUser) => {
    this.userAuthToken = loggedInUser.getAuthResponse().id_token;
    this.userDisplayName = loggedInUser.getBasicProfile().getName();
    console.log(this);
  }
Run Code Online (Sandbox Code Playgroud)

第二个问题正在发生,因为第三方脚本在Angular的上下文之外运行.Angular使用zones所以当你运行某些东西时,例如setTimeout(),在区域中运行猴子补丁,Angular会得到通知.您可以在区域中运行jQuery,如下所示:

  constructor(private zone: NgZone) {
    this.zone.run(() => {
      $.proxy(this.onGoogleLoginSuccess, this);
    });
  }
Run Code Online (Sandbox Code Playgroud)

关于该区域有很多问题/答案,如果您想了解更多,那么我的解释会更好,但如果您使用箭头功能,它不应该是您的示例的问题.


小智 8

如果你想要一个例子,我做了一个谷歌登录组件.

  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);
    });
  }
Run Code Online (Sandbox Code Playgroud)

这有点晚了,但我想举个例子,如果有人想用ng2登录api.