要将 gapi 和 gapi.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 包都包含在编译过程中。
-
node_modules/@types(任何封闭文件夹)中的包被视为可见。
-
但是。如果
types 已经指定,TSConfig Reference 说明您必须添加gapi 或gapi.auth2,否则默认情况下不会包含它们。在本节中,编辑 tsconfig.json 以包含新的 gapi 和 gapi.auth2 类型:
{
"compilerOptions": {
"types": ["jest", "lodash", "gapi", "gapi.auth2"]
}
}
此时,如果你有足够的动力,你可以阅读Typescript Module Resolution,你可以直接跳到Node.js如何解析模块:
Node 会在名为的特殊文件夹中查找您的模块
node_modules。 node_modules 文件夹可以与
当前文件,或目录链中更高的文件。节点会走上去
目录链,遍历每个 node_modules 直到找到
您尝试加载的模块。
因此,您不需要在 Angular2 服务或组件(或您使用 gapi 或 gapi.auth2 的任何地方)中添加对类型定义的引用。
但是,如果您确实添加了对 gapi 或 gapi.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.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);
});
});
}
请注意,我们的朋友 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/ 导入并将其保留为阻塞调用。