typescript 在angular 2打字稿中导入gapi.auth2

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38091215/
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:36:27  来源:igfitidea点击:

Import gapi.auth2 in angular 2 typescript

typescriptangulargapi

提问by Shu Lin

I tried to import some classes or function from Google gapi.auth2 in typescript. But below code never works even I correctly added the gapi.auth2 types in typings directory.

我试图在打字稿中从 Google gapi.auth2 导入一些类或函数。但是即使我在打字目录中正确添加了 gapi.auth2 类型,下面的代码也永远无法工作。

import { GoogleAuth } from 'gapi.auth2';

import { GoogleAuth } from 'gapi.auth2';

I always got error:

我总是有错误:

Error TS2307: Cannot find module 'gapi.auth2'

Error TS2307: Cannot find module 'gapi.auth2'

Shall I use some relative directory searching, such as '../../typings/gapi.auth2'?

我应该使用一些相对目录搜索,例如'../../typings/gapi.auth2'吗?

Or maybe the way I am using the gapi is totally wrong?

或者我使用gapi的方式是完全错误的?

Thanks!

谢谢!

回答by Hyman

To use gapiand gapi.authwith Angular2, install the type script definitions using NPM.

要使用gapigapi.auth与Angular2,使用NPM安装类型的脚本定义。

npm install --save @types/gapi
npm install --save @types/gapi.auth2

This will install two packages, @types/gapiand @types/gapi.auth2to the node_modulesfolder and save the configuration in package.json.

这将安装两个包,@types /gapi@types/gapi.auth2node_modules文件夹并将配置保存在package.json.

Inspect your node_modulesfolder to check they install correctly. If your Angular2 app is called main-app, you should see:

检查您的node_modules文件夹以检查它们是否正确安装。如果您的 Angular2 应用程序被称为主应用程序,您应该看到:

main-app/
  node_modules/
    @types/
      gapi/
      gapi.auth2/

Edit tsconfig.jsonto include new gapiand gapi.auth2types (below is just a excerpt):

编辑tsconfig.json以包含 newgapigapi.auth2types(以下只是摘录):

{
  "compileOnSave": false,
  "compilerOptions": {
    "types": ["gapi", "gapi.auth2"]
  }
}

At this point I highly recommend grabbing a coffee and reading Typescript Module Resolution, you can skip straight to How Node.js resolves modules:

在这一点上,我强烈建议您喝杯咖啡并阅读Typescript Module Resolution,您可以直接跳到Node.js 如何解析模块

[...] resolution for a non-relative module name is performed differently. Node will look for your modules in special folders named node_modules. A node_modulesfolder can be on the same level as the current file, or higher up in the directory chain. Node will walk up the directory chain, looking through each node_modulesuntil it finds the module you tried to load.

[...] 非相关模块名称的解析以不同方式执行。Node 将在名为 node_modules. 甲node_modules文件夹可以是在同一水平上作为当前文件或目录中的链越往上。Node 将沿着目录链向上走,查看每个目录,node_modules直到找到您尝试加载的模块。

For this reason, you shouldn't need to add a reference to the type definitions in your Angular2 Service or Component (or wherever you're using gapior gapi.auth2).

出于这个原因,您不应该需要在 Angular2 服务或组件(或您使用的任何地方gapigapi.auth2)中添加对类型定义的引用。

However, if you do add a reference to the gapior gapi.auth2TypeScript definitions, it must reference the .tsfile installed using npm install(note, you must keep the ///oherwise you'll get an error):

但是,如果您确实添加了对gapigapi.auth2TypeScript 定义的引用,则它必须引用.ts使用安装的文件npm install(注意,您必须保留///否则您将收到错误):

/// <reference path="../../node_modules/@types/gapi/index.d.ts" />

The path is relative, so yours may differ depending on where your .tsfile is relative to where you installed the TypeScript definitions.

该路径是相对的,因此您的路径可能会有所不同,具体取决于您的.ts文件相对于安装 TypeScript 定义的位置。

Whether you added an explicit reference or used TypeScript's Node module resolution mechanism, you still need to declare your variables in your .tsfile so Angular2 knows about the window gapivariable at compile time. Add declare var gapi: any;to your .tsfile but do notplace it within a class definition. I put mine just below any imports:

无论您添加了显式引用还是使用了 TypeScript 的 Node 模块解析机制,您仍然需要在.ts文件中声明您的变量,以便 Angular2gapi在编译时知道窗口变量。添加declare var gapi: any;到您的.ts文件,但没有一个类定义中把它。我把我的放在任何进口的下面:

// You may not have this explicit reference.
/// <reference path="../../node_modules/@types/gapi/index.d.ts" />
import { NgZone, Injectable, Optional } from '@angular/core';
declare var gapi: any;

Looking at the definitions themselves (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/gapi/index.d.ts), only functions are exported. Conversely, the interfaces are implementation details, so they are left un-exported and will not be visible to code outside the namespace.

查看定义本身(https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/gapi/index.d.ts),仅导出函数。相反,接口是实现细节,因此它们不会被导出并且对于命名空间之外的代码是不可见的。

Working with Other JavaScript Librariesin TypeScript documentationis worth reading to understand what we're getting with all this work.

与其他JavaScript库工作打字稿文档是值得一读,以了解我们正在与所有这些工作得到。

Next, load the gapiclient from your own function (possibly in an Angular Service):

接下来,gapi从您自己的函数(可能在 Angular 服务中)加载客户端:

 loadClient(): Promise<any> {
     return new Promise((resolve, reject) => {
         this.zone.run(() => {
                gapi.load('client', {
                    callback: resolve,
                    onerror: reject,
                    timeout: 1000, // 5 seconds.
                    ontimeout: reject
                });
         });
    });
}

This function is non-trivial, here's why...

这个功能很重要,这就是为什么......

Firstly, note we're calling gapi.loadwith a configuration objectand not a callback. The GAPI referencestates either can be used:

首先,请注意我们调用gapi.load配置对象,而不是一个回调。所述GAPI参考状态可以使用任一种:

  • A callback function that is called when the libraries have finished loading.
  • An object encapsulating the various configuration parameters for this method. Only callback is required.
  • 库加载完成后调用的回调函数。
  • 封装此方法的各种配置参数的对象。只需要回调。

Using a configuration option allows us to rejectthe Promise when loading the library times-out, or just errors. In my experience, loading the library fails more often than initializing it - which is why configuration object is better than just a callback.

使用配置选项允许我们在加载库超时或错误时拒绝Promise。根据我的经验,加载库比初始化它失败的频率更高——这就是为什么配置对象比回调更好的原因。

Secondly, we're wrapping gapi.loadin

其次,我们是包装gapi.load

this.zone.run(() => {
  // gapi.load
});

NgZone.run is documentedand states

NgZone.run 已记录并说明

Running functions via zone.runallows you to reenter Angular zone from a task that was executed outside of the Angular zone [...]

运行函数通过zone.run允许您从在 Angular 区域之外执行的任务重新进入 Angular 区域 [...]

This is exactly what we want since the call to gapi.loadleaves the Angular zone. Omitting this can leave to very funky results that can be hard to debug.

这正是我们想要的,因为调用gapi.load离开了 Angular 区域。忽略这一点可能会导致很难调试的非常时髦的结果。

Thirdly, loadClient()returns a promise that is resolved - allowing the caller to choose how they handle gapi.load. For example if our loadClientmethod belonged to an Angular service, apiLoaderServce, a component may use ngOnInitto load gapi:

第三,loadClient()返回一个已解决的承诺——允许调用者选择他们如何处理gapi.load。例如,如果我们的loadClient方法属于 Angular 服务,则apiLoaderServce组件可能用于ngOnInit加载gapi

ngOnInit(): void {
    this.apiLoaderService.loadClient().then(
        result => this.apiLoaded = true,
        err => this.apiLoaded = false
    );
}

Once gapi.loadhas been called, gapi.clientwill be ready and you should use it to initializes the JavaScript client with you API key, OAuth client ID, scope, and API discovery document(s):

一旦gapi.load被调用,gapi.client将准备就绪,您应该使用它来使用您的 API 密钥、OAuth 客户端 ID、范围和 API 发现文档来初始化 JavaScript 客户端:

initClient(): Promise<any> {
    var API_KEY = // Your API key.
    var DISCOVERY_DOC = // Your discovery doc URL.
    var initObj = {
        'apiKey': API_KEY,
        'discoveryDocs': [DISCOVERY_DOC],
    };

    return new Promise((resolve, reject) => {
        this.zone.run(() => {
            gapi.client.init(initObj).then(resolve, reject);
        });
    });
}

Notice our friend NgZone.runis used once again to ensure the Angular Zone is re-entered.

请注意,我们的朋友NgZone.run再次被使用以确保重新进入 Angular Zone。

In practice, I add loadClient()and initClient()to an Angular Service. In a high-level Angular component (usually just below the app-component) I load and initialize in ngOnInit:

在实践中,我将loadClient()和添加initClient()到 Angular 服务。在高级 Angular 组件(通常就在 app 组件下方)中,我加载并初始化ngOnInit

ngOnInit(): void {
    this.apiLoaderService.loadClient().then(
        result => {
            this.apiLoaded = true;
            return this.apiLoaderService.initClient()
        },
        err => {
            this.apiFailed = true;
        }
    ).then(result => {
        this.apiReady = true;
    }, err => {
        this.apiFailed = true;
    });
}

Lastly, you need to add the gapi script file to your file.

最后,您需要将gapi 脚本文件添加到您的文件中。

<html>
  <head>
    <script src="https://apis.google.com/js/api.js"></script>

You must not use the asyncordeferattributes since they will cause the Angular world to enter before the gapi library has loaded.

你不能使用asyncdefer属性,因为它们会导致在 gapi 库加载之前进入 Angular 世界。

<!-- This will not work. -->
<html>
  <head>
    <script async defer src="https://apis.google.com/js/api.js"></script>

I previously suggested keeping page-load speeds fast by loading a local, minified copy of the gapi libraryin the /main-app/src/assestsfolder and importing:

我之前建议通过在文件夹中加载本地、缩小的gapi 库副本/main-app/src/assests并导入来保持页面加载速度快:

    <html>
      <head>
        <script src="assets/api.js"></script>

However, I stronglyrecommend not doingthis. Google may update https://apis.google.com/js/api.jsand your client will break. I have been caught-out by this twice. In the end it was better just to import from //apis.google.com/js/and keep it as a blocking call.

但是,我强烈建议不要这样做。Google 可能会更新https://apis.google.com/js/api.js并且您的客户端会中断。我已经被这两次抓住了。最后,最好只导入 from//apis.google.com/js/并将其保留为阻塞调用。

回答by StephenSolace

This is modified from @Hyman'sanswer to use the RxJS library. While the original question asks for Angular 2, I'm using Angular 5 here in case anyone's working with an updated version.

这是从@Hyman回答修改而来的,以使用 RxJS 库。虽然最初的问题要求使用 Angular 2,但我在这里使用的是 Angular 5,以防有人使用更新版本。

  1. The first step is the same, downloading the gapi types with npm.

    npm install --save @types/gapi
    npm install --save @types/gapi.auth2
    
  2. You will need to update your tsconfig.json. If you're having issues, you may also need to update tsconfig.app.json and tsconfig.spec.json. They inherit from tsconfig.json, but if you specify types, I think they may overwrite the base. Snippet below:

    "typeRoots": [
      "node_modules/@types"
    ],
    "types": [
      "gapi",
      "gapi.auth2"
    ],
    "lib": [
      "es2017",
      "dom"
    ]
    
  3. Add a reference to Google's platform.js. I put mine in index.html. I left out asyncand deferas @Hymanrecommended.

    <script src="https://apis.google.com/js/platform.js"></script>
    
  4. Next create an authentication service. The complete code is here:

    import { Injectable, NgZone, Output } from '@angular/core';
    import { Observable } from 'rxjs/Observable';
    import { BehaviorSubject } from 'rxjs';
    import { HttpClient } from '@angular/common/http';
    import { User } from './User';
    
    @Injectable()
    export class AuthenticatorService {
        public auth2: any;
        public user$: BehaviorSubject<User> = new BehaviorSubject<User>(null);
        public isLoggedIn$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
        public isLoaded$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
    
        constructor(private zone: NgZone, private http: HttpClient) { }
    
        validateToken(token: string): Observable<User> {
            return this.http.get<User>(`http://yourServer:3000/validationApi/${token}`);
        }
    
        signIn(): void {
            this.auth2.signIn().then(user => {
                this.validateToken(user.getAuthResponse().id_token).subscribe(user => {
                    this.zone.run(() => {
                        this.user$.next(user);
                        this.isLoggedIn$.next(true);
                    });
                },
                    (err) => {
                        console.error(err);
                    });
            });
        };
    
        signOut(): void {
            this.auth2.signOut().then(() => {
                this.zone.run(() => {
                    this.isLoggedIn$.next(false);
                    this.user$.next(null);
                });
            },
                (err) => {
                    console.error(err);
                });
        }
    
        loadAuth2(): void {
            gapi.load('auth2', () => {
                gapi.auth2.init({
                    client_id: 'yourClientId',
                    fetch_basic_profile: true
                }).then((auth) => {
                    this.zone.run(() => {
                        this.auth2 = auth;
                        this.isLoaded$.next(true);
                    });
                },
                );
            });
        }
    }
    
  1. 第一步也是一样,用npm下载gapi类型。

    npm install --save @types/gapi
    npm install --save @types/gapi.auth2
    
  2. 您将需要更新您的 tsconfig.json。如果您遇到问题,您可能还需要更新 tsconfig.app.json 和 tsconfig.spec.json。它们继承自 tsconfig.json,但如果您指定类型,我认为它们可能会覆盖基础。下面的片段:

    "typeRoots": [
      "node_modules/@types"
    ],
    "types": [
      "gapi",
      "gapi.auth2"
    ],
    "lib": [
      "es2017",
      "dom"
    ]
    
  3. 添加对 Google 的platform.js. 我把我的放进去index.html。我遗漏了asyncdefer正如@Hyman推荐的那样。

    <script src="https://apis.google.com/js/platform.js"></script>
    
  4. 接下来创建一个身份验证服务。完整的代码在这里:

    import { Injectable, NgZone, Output } from '@angular/core';
    import { Observable } from 'rxjs/Observable';
    import { BehaviorSubject } from 'rxjs';
    import { HttpClient } from '@angular/common/http';
    import { User } from './User';
    
    @Injectable()
    export class AuthenticatorService {
        public auth2: any;
        public user$: BehaviorSubject<User> = new BehaviorSubject<User>(null);
        public isLoggedIn$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
        public isLoaded$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
    
        constructor(private zone: NgZone, private http: HttpClient) { }
    
        validateToken(token: string): Observable<User> {
            return this.http.get<User>(`http://yourServer:3000/validationApi/${token}`);
        }
    
        signIn(): void {
            this.auth2.signIn().then(user => {
                this.validateToken(user.getAuthResponse().id_token).subscribe(user => {
                    this.zone.run(() => {
                        this.user$.next(user);
                        this.isLoggedIn$.next(true);
                    });
                },
                    (err) => {
                        console.error(err);
                    });
            });
        };
    
        signOut(): void {
            this.auth2.signOut().then(() => {
                this.zone.run(() => {
                    this.isLoggedIn$.next(false);
                    this.user$.next(null);
                });
            },
                (err) => {
                    console.error(err);
                });
        }
    
        loadAuth2(): void {
            gapi.load('auth2', () => {
                gapi.auth2.init({
                    client_id: 'yourClientId',
                    fetch_basic_profile: true
                }).then((auth) => {
                    this.zone.run(() => {
                        this.auth2 = auth;
                        this.isLoaded$.next(true);
                    });
                },
                );
            });
        }
    }
    

We have a lot happening here. Start by taking notice of the RxJS BehaviorSubjects. We'll be using these to notify our components of changes. Our loadAuth2function uses Google's library to get a gapi.auth2.GoogleAuthobject. If you need more information on Google's authentication library please check out their introductionor their documentation. Note we're using this.zone.runonce we get our GoogleAuthobject back. Running the entire function in an NgZoneled to unexpected behavior for me.Next we take an RxJS BehaviorSubjectisLoaded$and set the value to true. You'll see similar behavior in the signIn()and signOut()functions- taking the results and running them in an NgZoneand updating our appropriate BehaviorSubject.

我们这里发生了很多事情。首先注意 RxJS BehaviorSubjects。我们将使用这些来通知我们的组件更改。我们的loadAuth2函数使用 Google 的库来获取gapi.auth2.GoogleAuth对象。如果您需要有关 Google 身份验证库的更多信息,请查看他们的介绍文档。请注意,this.zone.run一旦我们取回我们的GoogleAuth对象,我们就会使用它。运行整个函数NgZone导致了我意想不到的行为。接下来我们使用一个 RxJSBehaviorSubjectisLoaded$并将值设置为 true。您将在signIn()signOut()函数中看到类似的行为 -获取结果并在 中运行它们NgZone并更新我们适当的BehaviorSubject.

  1. Now that we have our service, it's time to use it. We'll create a component for signing in and out. The code's below:

    import { Component, OnInit } from '@angular/core';
    import { AuthenticatorService } from  '../authenticator.service'
    import { User } from '../User';
    
    
    @Component({
    selector: 'sign-in',
    template: `
        <ng-container *ngIf="authIsLoaded">
             <button *ngIf="!isLoggedIn" (click)="signIn()">Sign In With Google</button>
            <button *ngIf="isLoggedIn" (click)="signOut()">Sign Out</button>
        </ng-container>
        <h2 *ngIf="authIsLoaded && isLoggedIn"> Signed in as {{user.name}} </h2>`
    })
    export class GoogleAuthenticatorComponent implements OnInit {
    
    public authIsLoaded: boolean = false;
    public isLoggedIn: boolean = false;
    public user: User;
    
    constructor(private authenticatorService: AuthenticatorService) { }
    
        signIn(): void {
        this.authenticatorService.signIn();
        };
    
        signOut(): void {
        this.authenticatorService.signOut();
        }
    
        ngOnInit() {
        this.authenticatorService.isLoaded$.subscribe( value => {
            this.authIsLoaded = value;
        });
    
        this.authenticatorService.isLoggedIn$.subscribe( value => {
            this.isLoggedIn = value;
        });
    
        this.authenticatorService.user$.subscribe( value => {
            this.user = value;
        });
    
        this.authenticatorService.loadAuth2();
        }
    }
    
  1. 现在我们有了我们的服务,是时候使用它了。我们将创建一个用于登录和注销的组件。代码如下:

    import { Component, OnInit } from '@angular/core';
    import { AuthenticatorService } from  '../authenticator.service'
    import { User } from '../User';
    
    
    @Component({
    selector: 'sign-in',
    template: `
        <ng-container *ngIf="authIsLoaded">
             <button *ngIf="!isLoggedIn" (click)="signIn()">Sign In With Google</button>
            <button *ngIf="isLoggedIn" (click)="signOut()">Sign Out</button>
        </ng-container>
        <h2 *ngIf="authIsLoaded && isLoggedIn"> Signed in as {{user.name}} </h2>`
    })
    export class GoogleAuthenticatorComponent implements OnInit {
    
    public authIsLoaded: boolean = false;
    public isLoggedIn: boolean = false;
    public user: User;
    
    constructor(private authenticatorService: AuthenticatorService) { }
    
        signIn(): void {
        this.authenticatorService.signIn();
        };
    
        signOut(): void {
        this.authenticatorService.signOut();
        }
    
        ngOnInit() {
        this.authenticatorService.isLoaded$.subscribe( value => {
            this.authIsLoaded = value;
        });
    
        this.authenticatorService.isLoggedIn$.subscribe( value => {
            this.isLoggedIn = value;
        });
    
        this.authenticatorService.user$.subscribe( value => {
            this.user = value;
        });
    
        this.authenticatorService.loadAuth2();
        }
    }
    

The most important part here is the ngOnInitimplementation. This is where we'll subscribe to the AuthenticatorService's changes and update the view accordingly.

这里最重要的部分是ngOnInit实现。这是我们将订阅 AuthenticatorService 的更改并相应地更新视图的地方。

Hope these steps help someone out there to set up gapi.auth2 in their project.

希望这些步骤可以帮助那里的人在他们的项目中设置 gapi.auth2。