我尝试在typescript中从Google gapi.auth2导入一些类或函数。但是,即使我在typings目录中正确添加了gapi.auth2类型,代码也永远无法正常工作。
import { GoogleAuth } from 'gapi.auth2';
我总是有错误:
Error TS2307: Cannot find module 'gapi.auth2'
我应该使用一些相对目录搜索,例如'../../ typings / gapi.auth2'?
或许我使用gapi的方式完全错了?
谢谢!
答案 0 :(得分:36)
要在Angular2中使用gapi
和gapi.auth
,请使用NPM安装类型脚本定义。
npm install --save @types/gapi
npm install --save @types/gapi.auth2
这会将@types/gapi和@types/gapi.auth2两个软件包安装到node_modules
文件夹,并将配置保存在package.json
。
检查您的node_modules
文件夹,检查它们是否正确安装。如果您的Angular2应用程序名为main-app,您应该看到:
main-app/
node_modules/
@types/
gapi/
gapi.auth2/
修改tsconfig.json
以包含新的gapi
和gapi.auth2
类型(以下只是摘录):
{
"compileOnSave": false,
"compilerOptions": {
"types": ["gapi", "gapi.auth2"]
}
}
此时我强烈建议您抓一杯咖啡阅读Typescript Module Resolution,您可以直接跳到 Node.js如何解析模块:
[...]执行非相对模块名称的解析 不同。 Node将在名为的特殊文件夹中查找您的模块
node_modules
。node_modules
文件夹可以与...处于同一级别 当前文件,或目录链中的更高版本。节点将走了 目录链,查看每个node_modules
直到找到它 您尝试加载的模块。
因此,您不需要在Angular2服务或组件中添加对类型定义的引用(或者您使用gapi
或gapi.auth2
的任何地方)。
但是,如果您确实添加了对gapi
或gapi.auth2
TypeScript定义的引用,则必须引用使用.ts
安装的npm install
文件(注意,您必须保留///
或者你会收到错误):
/// <reference path="../../node_modules/@types/gapi/index.d.ts" />
路径是相对路径,因此您的路径可能会有所不同,具体取决于.ts
文件相对于您安装TypeScript定义的位置。
无论是添加显式引用还是使用TypeScript的节点模块解析机制,您仍需要在.ts
文件中声明变量,以便Angular2在编译时知道窗口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;
查看定义本身(https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/gapi/index.d.ts),只导出函数。相反,接口是实现细节,因此它们不会被导出,并且对命名空间外的代码不可见。
在TypeScript documentation中使用其他JavaScript库值得一读,以了解我们在完成所有这些工作后所获得的目标。
接下来,使用您自己的函数加载gapi
客户端(可能在Angular Service中):
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
});
通过
之外执行的任务zone.run
运行功能可让您重新输入Angular区域 在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组件中(通常在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;
});
}
最后,您需要将gapi脚本文件添加到您的文件中。
<html>
<head>
<script src="https://apis.google.com/js/api.js"></script>
您不得使用async
或 defer
属性,因为在gapi加载之前,任何一个都会导致Angular 2世界进入。
<!-- 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>
但是,强烈建议不要这个。 Google可能会更新https://apis.google.com/js/api.js,您的客户将会中断。我被这两次抓住了。最后,最好从//apis.google.com/js/
导入并将其保留为阻止调用。
答案 1 :(得分:4)
这是从@Jack's回复修改为使用RxJS库。虽然原始问题要求Angular 2,但我在这里使用Angular 5以防任何人使用更新版本。
第一步是相同的,用npm下载gapi类型。
String.Concat
您需要更新tsconfig.json。如果您遇到问题,可能还需要更新tsconfig.app.json和tsconfig.spec.json。它们继承自tsconfig.json,但是如果你指定类型,我认为它们可能会覆盖基础。下面的代码段:
StringBuilder
添加对Google npm install --save @types/gapi
npm install --save @types/gapi.auth2
的引用。我把我放在"typeRoots": [
"node_modules/@types"
],
"types": [
"gapi",
"gapi.auth2"
],
"lib": [
"es2017",
"dom"
]
。我将platform.js
和index.html
遗漏为@Jack推荐。
async
接下来创建一个身份验证服务。完整的代码在这里:
defer
我们在这里发生了很多事情。首先注意RxJS BehaviorSubjects。我们将使用这些来通知我们的组件变更。我们的<script src="https://apis.google.com/js/platform.js"></script>
函数使用Google的库来获取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);
});
},
);
});
}
}
个对象。如果您需要有关Google身份验证库的更多信息,请查看their introduction或their documentation。请注意,一旦我们收到loadAuth2
对象,我们就会使用gapi.auth2.GoogleAuth
。 在this.zone.run
中运行整个功能会导致我出现意外行为。接下来我们使用RxJS GoogleAuth
NgZone
并将值设置为true。您会在BehaviorSubject
和isLoaded$
函数中看到类似行为 - 获取结果并在signIn()
中运行并更新相应的signOut()
。
现在我们有了我们的服务,现在是时候使用它了。我们将创建一个用于登录和注销的组件。代码如下:
NgZone
这里最重要的部分是BehaviorSubject
实施。这是我们订阅AuthenticatorService的更改并相应地更新视图的地方。
希望这些步骤可以帮助那些人在他们的项目中设置gapi.auth2。
答案 2 :(得分:0)
编译器将尝试查找环境模块声明。 所以你应该安装: