【问题标题】:Import gapi.auth2 in angular 2 typescript在 Angular 2 打字稿中导入 gapi.auth2
【发布时间】:2016-10-31 16:00:04
【问题描述】:

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

import { GoogleAuth } from 'gapi.auth2';

我总是出错:

Error TS2307: Cannot find module 'gapi.auth2'

我要不要使用一些相对目录搜索,比如../../typings/gapi.auth2

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

谢谢!

【问题讨论】:

标签: typescript angular google-api-js-client


【解决方案1】:

要将 gapigapi.auth 与 Angular2 一起使用,请使用 NPM 安装类型脚本定义。

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

这会将@types/gapi@types/gapi.auth2这两个包安装到node_modules文件夹,并将配置保存在package.json中。

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

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

red pill and blue pill 场景:

  • 如果还没有提供任何types compiler options,则无需在tsconfig.json"types": [] 编译器选项中显式添加gapigapi.auth2,因为
  1. 默认情况下,所有可见的@types 包都包含在编译过程中。
  2. node_modules/@types(任何封闭文件夹)中的包被视为可见。
  • 但是。如果types 已经指定,TSConfig Reference 说明您必须添加gapigapi.auth2,否则默认情况下不会包含它们。在本节中,编辑 tsconfig.json 以包含新的 gapigapi.auth2 类型:
{
   "compilerOptions": {
     "types": ["jest", "lodash", "gapi", "gapi.auth2"]
    }
}

此时,如果你有足够的动力,你可以阅读Typescript Module Resolution,你可以直接跳到Node.js如何解析模块

Node 会在名为的特殊文件夹中查找您的模块 node_modulesnode_modules 文件夹可以与 当前文件,或目录链中更高的文件。节点会走上去 目录链,遍历每个 node_modules 直到找到 您尝试加载的模块。

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

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

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

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

无论您添加了显式引用还是使用了 TypeScript 的 Node 模块解析机制,您仍然需要在 .ts 文件中声明变量,以便 Angular 在编译时知道窗口 gapi 变量。将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;
TypeScript documentation 中的

使用其他 JavaScript 库值得一读,以了解我们通过所有这些工作得到了什么。

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

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

这个函数很重要,这就是为什么......

首先,请注意我们使用配置对象而不是回调调用gapi.load。可以使用GAPI reference 状态:

  • 库完成时调用的回调函数 正在加载。
  • 封装各种配置参数的对象 对于这种方法。只需要回调。

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

其次,我们将gapi.load 包装在

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

NgZone.run is documented 和状态

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

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

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

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

一旦调用了gapi.loadgapi.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);
        });
    });
}

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

在实践中,我将 loadClient()initClient() 添加到 Angular 服务中。在高级 Angular 组件中(通常就在 app-component 的下方)我在 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;
    });
}

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

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

您不得使用async defer 属性,因为它们会导致在gapi 库加载之前进入Angular 世界。

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

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

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

但是,我强烈建议不要这样做。谷歌可能会更新https://apis.google.com/js/api.js,你的客户端就会崩溃。我已经被这两次抓住了。最后,最好从//apis.google.com/js/ 导入并将其保留为阻塞调用。

【讨论】:

  • 安装@types/gapi@types/gapi.auth2 后,如何在代码中使用这些类型?例如,您将承诺定义为Promise&lt;any&gt;。我怎么能做类似Promise&lt;GoogleAuth&gt; 的事情?
  • @zanderle 将其用作gapi.auth2.GoogleAuth,另外,如果您像我一样使用 angular-cli 开始了一个新项目,webpack 应该为找不到 gapi 命名空间而哭泣,将类型添加到 tsconfig .app.json。因为 Angular 团队决定他们不需要自动的 ../node_modules/@types 包含,这会在 webpack 打包时搞砸。
  • 首先非常感谢您的深入演练。添加到这个漂亮的作品,你也可以直接使用/// &lt;reference types="gapi" /&gt;reference an @types package
  • 非常感谢@Jack,截至 2019 年 12 月,这个答案仍然有效(并且教了很多)。:)
  • 试了一切都没有成功 错误 TS2503: 找不到命名空间 'gapi' 这东西肯定有变化或其他东西
【解决方案2】:

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

  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 作为推荐的@Jack

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

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

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

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

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

【讨论】:

  • 赞成角实现。我知道我参加聚会有点晚了,但是您可以提供一个简单的回购链接和一个工作示例吗?我正在努力使用 gapi 日历,任何帮助将不胜感激。
  • 什么是User
  • 你能描述一下“用户”对象吗?
  • 用户可以是任何东西...我通过剥离 ValidateToken 方法(在我自己的用例中,我通过 aws DynamoDB api - 而不是 HttpClient 进行验证)并创建一个空用户类:导出类用户{}。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-16
  • 2019-01-03
  • 2017-05-02
  • 2021-02-13
  • 1970-01-01
  • 1970-01-01
  • 2020-04-19
相关资源
最近更新 更多