使用 Typescript 登录网站和 Angular 2

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35530483/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 03:18:02  来源:igfitidea点击:

Google Sign-In for Websites and Angular 2 using Typescript

javascripttypescriptangulargoogle-login

提问by Craig Phillips

I'm building a site that has a pretty standard RESTful web service to handle persistence and complex business logic. The UI I'm building to consume this service is using Angular 2with components written in TypeScript.

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

Rather than build my own authentication system, I'm hoping to rely on Google Sign-In for Websites. The idea being that users will come to the site, sign in via the framework provided there and then send along the resulting ID tokens, which the server hosting the RESTful service can then verify.

我希望依靠 Google Sign-In for Websites,而不是构建我自己的身份验证系统。这个想法是用户将来到该站点,通过那里提供的框架登录,然后发送生成的 ID 令牌,然后托管 RESTful 服务的服务器可以验证这些令牌。

In the Google Sign-In documentation there are instructions for creating the login button via JavaScriptwhich is what needs to happen since the login button is being rendered dynamically in an Angular template. The relevant portion of the template:

在 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>

And the Angular 2 component definition in Typescript:

以及 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);
  }
}

The basic flow goes:

基本流程如下:

  1. Angular renders the template and the message "Hello, empty!" is shown.
  2. The ngAfterViewInithook is fired and the gapi.signin2.render(...)method is called which converts the empty div into a Google login button. This works correctly and clicking on that button will trigger the login process.
  3. This also attaches the component's onGoogleLoginSuccessmethod to actually process the returned token after a user logs in.
  4. Angular detects that the userDisplayNameproperty has changed and updates the page to now display "Hello, Craig (or whatever your name is)!".
  1. Angular 渲染模板和消息“你好,空!” 显示。
  2. ngAfterViewInit钩点火,gapi.signin2.render(...)方法被称为该空div转换成谷歌登录按钮。这工作正常,单击该按钮将触发登录过程。
  3. 这也附加了组件的onGoogleLoginSuccess方法,以在用户登录后实际处理返回的令牌。
  4. Angular 检测到userDisplayName属性已更改并更新页面,现在显示“你好,克雷格(或任何你的名字)!”。

The first problem that occurs is in the onGoogleLoginSuccessmethod. Notice the console.log(...)calls in the constructorand in that method. As expected, the one in the constructorreturns the Angular component. The one in the onGoogleLoginSuccessmethod, however, returns the JavaScript windowobject.

发生的第一个问题是在onGoogleLoginSuccess方法中。注意该方法console.log(...)中的constructor和 中的调用。正如预期的那样,中的那个constructor返回 Angular 组件。onGoogleLoginSuccess但是,该方法中的一个返回 JavaScriptwindow对象。

So it looks like the context is getting lost in the process of hopping out to Google's login logic so my next step was to try incorporating jQuery's $.proxycall to hang on to the correct context. So I import the jQuery namespace by adding declare var $:any;to the top of the component and then convert the contents of the ngAfterViewInitmethod to be:

所以看起来上下文在跳出到 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"
      });
}

After adding that, the two console.logcalls return the same object so property values are now updating correctly. The second log message shows the object with the expected updated property values.

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

Unfortunately, the Angular template does not get updated when this happens. While debugging, I stumbled on something that I believe explains what is going on. I added the following line to the end of the ngAfterViewInithook:

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

setTimeout(function() {
  this.googleLoginButtonId = this.googleLoginButtonId },
  5000);

This shouldn't really do anything. It just waits five seconds after the hook ends and then sets a property value equal to itself. However, with the line in place the "Hello, empty!"message turns into "Hello, Craig!"about five seconds after the page has loaded. This suggest to me that Angular just isn't noticing that the property values are changing in the onGoogleLoginSuccessmethod. So when something else happens to notify Angular that property values have changed (such as the otherwise useless self-assignment above), Angular wakes up and updates everything.

这真的不应该做任何事情。它只是在钩子结束后等待五秒钟,然后设置一个等于它自己的属性值。但是,在该行到位后,该"Hello, empty!"消息会"Hello, Craig!"在页面加载后大约五秒钟。这向我表明 Angular 只是没有注意到方法中的属性值正在发生变化onGoogleLoginSuccess。因此,当其他事情发生通知 Angular 属性值已更改时(例如上面无用的自赋值),Angular 会唤醒并更新所有内容。

Obviously that's not a hack I want to leave in place so I'm wondering if any Angular experts out there can clue me in? Is there some call I should be making to force Angular to notice some properties have changed?

显然,这不是我想留在原地的黑客,所以我想知道是否有任何 Angular 专家可以为我提供线索?是否应该进行一些调用以强制 Angular 注意到某些属性已更改?

UPDATED 2016-02-21 to provided clarity on the specific answer that solved the problem

更新 2016-02-21 以明确解决问题的具体答案

I ended up needing to use both pieces of the suggestion provided in the selected answer.

我最终需要使用所选答案中提供的两条建议。

First, exactly as suggested, I needed to convert the onGoogleLoginSuccessmethod to use an arrow function. Secondly, I needed to make use of an NgZoneobject to make sure that the property updates occurred in a context of which Angular is aware. So the final method ended up looking like

首先,完全按照建议,我需要将该onGoogleLoginSuccess方法转换为使用箭头函数。其次,我需要使用一个NgZone对象来确保属性更新发生在 Angular 知道的上下文中。所以最终的方法看起来像

onGoogleLoginSuccess = (loggedInUser) => {
    this._zone.run(() => {
        this.userAuthToken = loggedInUser.getAuthResponse().id_token;
        this.userDisplayName = loggedInUser.getBasicProfile().getName();
    });
}

I did need to import the _zoneobject: import {Component, NgZone} from "angular2/core";

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

I also needed to inject it as suggested in the answer via the class's contructor: constructor(private _zone: NgZone) { }

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

采纳答案by Sasxa

For your first problem solution is to use arrow functionwhich will preserve context of this:

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

  onGoogleLoginSuccess = (loggedInUser) => {
    this.userAuthToken = loggedInUser.getAuthResponse().id_token;
    this.userDisplayName = loggedInUser.getBasicProfile().getName();
    console.log(this);
  }

Second issue is happening because third-party scripts run outside the context of Angular. Angular uses zonesso when you run something, for example setTimeout(), which is monkey-patched to run in the zone, Angular will get notified. You would run jQuery in zone like this:

第二个问题正在发生,因为第三方脚本在 Angular 的上下文之外运行。Angular 使用,zones所以当你运行一些东西时,例如setTimeout(),它被猴子修补到在区域中运行,Angular 会得到通知。你会像这样在 zone 中运行 jQuery:

  constructor(private zone: NgZone) {
    this.zone.run(() => {
      $.proxy(this.onGoogleLoginSuccess, this);
    });
  }

There are many questions/answers about the zone with much better explanations then mine, if you want to know more, but it shouldn't be an issue for your example if you use arrow function.

如果您想了解更多信息,有很多关于该区域的问题/答案比我的解释要好得多,但是如果您使用箭头函数,这对于您的示例来说应该不是问题。

回答by Fr4NgUs

I made a google-login component if you want an example.

如果你想要一个例子,我做了一个 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);
    });
  }

It's a bit late but I just want to give an example if someone want to use google login api with ng2.

有点晚了,但我只想举个例子,如果有人想用 ng2 使用谷歌登录 api。

回答by Mubashir

Include the below file in your index.html

在 index.html 中包含以下文件

<script src="https://apis.google.com/js/platform.js" async defer></script>

login.html

登录.html

<button id="glogin">google login</button>

login.ts

登录.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));
      });

 }

回答by Rudraksh Pathak

Try this package - npm install angular2-google-login

试试这个包 - npm install angular2-google-login

Github - https://github.com/rudrakshpathak/angular2-google-login

Github - https://github.com/rudrakshpathak/angular2-google-login

I've implemented Google login in Angular2. Just import the package and you are ready to go.

我已经在 Angular2 中实现了 Google 登录。只需导入包,您就可以开始了。

Steps -

脚步 -

import { AuthService, AppGlobals } from 'angular2-google-login';

import { AuthService, AppGlobals } from 'angular2-google-login';

Supply providers -providers: [AuthService];

供应商——providers: [AuthService];

Constructor -constructor(private _googleAuth: AuthService){}

构造函数 -constructor(private _googleAuth: AuthService){}

Set Google client ID -AppGlobals.GOOGLE_CLIENT_ID = 'SECRET_CLIENT_ID';

设置 Google 客户端 ID -AppGlobals.GOOGLE_CLIENT_ID = 'SECRET_CLIENT_ID';

Use this to call the service -

使用它来调用服务 -

this._googleAuth.authenticateUser(()=>{
  //YOUR_CODE_HERE 
});

To logout -

登出 -

this._googleAuth.userLogout(()=>{
  //YOUR_CODE_HERE 
});

回答by Simon245

The selected answer by Sasxa also helped me but I found that I can bind this to the onSuccess function using .bind(this), that way I don't have to create the function with a fat arrow.

Sasxa 选择的答案也对我有所帮助,但我发现我可以使用.bind(this)将其绑定到 onSuccess 函数,这样我就不必使用箭头创建函数。

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"
    });
}