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
Import gapi.auth2 in angular 2 typescript
提问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 gapi
and gapi.auth
with Angular2, install the type script definitions using NPM.
要使用gapi
和gapi.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_modules
folder and save the configuration in package.json
.
这将安装两个包,@types /gapi和@types/gapi.auth2到node_modules
文件夹并将配置保存在package.json
.
Inspect your node_modules
folder 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.json
to include new gapi
and gapi.auth2
types (below is just a excerpt):
编辑tsconfig.json
以包含 newgapi
和gapi.auth2
types(以下只是摘录):
{
"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
. Anode_modules
folder 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 eachnode_modules
until 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 gapi
or gapi.auth2
).
出于这个原因,您不应该需要在 Angular2 服务或组件(或您使用的任何地方gapi
或gapi.auth2
)中添加对类型定义的引用。
However, if you do add a reference to the gapi
or gapi.auth2
TypeScript definitions, it must reference the .ts
file installed using npm install
(note, you must keep the ///
oherwise you'll get an error):
但是,如果您确实添加了对gapi
或gapi.auth2
TypeScript 定义的引用,则它必须引用.ts
使用安装的文件npm install
(注意,您必须保留///
否则您将收到错误):
/// <reference path="../../node_modules/@types/gapi/index.d.ts" />
The path is relative, so yours may differ depending on where your .ts
file 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 .ts
file so Angular2 knows about the window gapi
variable at compile time. Add declare var gapi: any;
to your .ts
file 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 gapi
client 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.load
in
其次,我们是包装gapi.load
在
this.zone.run(() => {
// gapi.load
});
NgZone.run is documentedand states
Running functions via
zone.run
allows 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.load
leaves 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 loadClient
method belonged to an Angular service, apiLoaderServce
, a component may use ngOnInit
to 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.load
has been called, gapi.client
will 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 async
ordefer
attributes since they will cause the Angular world to enter before the gapi library has loaded.
你不能使用async
或defer
属性,因为它们会导致在 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/assests
folder 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,以防有人使用更新版本。
The first step is the same, downloading the gapi types with npm.
npm install --save @types/gapi npm install --save @types/gapi.auth2
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" ]
Add a reference to Google's
platform.js
. I put mine inindex.html
. I left outasync
anddefer
as @Hymanrecommended.<script src="https://apis.google.com/js/platform.js"></script>
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); }); }, ); }); } }
第一步也是一样,用npm下载gapi类型。
npm install --save @types/gapi npm install --save @types/gapi.auth2
您将需要更新您的 tsconfig.json。如果您遇到问题,您可能还需要更新 tsconfig.app.json 和 tsconfig.spec.json。它们继承自 tsconfig.json,但如果您指定类型,我认为它们可能会覆盖基础。下面的片段:
"typeRoots": [ "node_modules/@types" ], "types": [ "gapi", "gapi.auth2" ], "lib": [ "es2017", "dom" ]
添加对 Google 的
platform.js
. 我把我的放进去index.html
。我遗漏了async
,defer
正如@Hyman推荐的那样。<script src="https://apis.google.com/js/platform.js"></script>
接下来创建一个身份验证服务。完整的代码在这里:
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 loadAuth2
function uses Google's library to get a gapi.auth2.GoogleAuth
object. If you need more information on Google's authentication library please check out their introductionor their documentation. Note we're using this.zone.run
once we get our GoogleAuth
object back. Running the entire function in an NgZone
led to unexpected behavior for me.Next we take an RxJS BehaviorSubject
isLoaded$
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 NgZone
and updating our appropriate BehaviorSubject
.
我们这里发生了很多事情。首先注意 RxJS BehaviorSubjects。我们将使用这些来通知我们的组件更改。我们的loadAuth2
函数使用 Google 的库来获取gapi.auth2.GoogleAuth
对象。如果您需要有关 Google 身份验证库的更多信息,请查看他们的介绍或文档。请注意,this.zone.run
一旦我们取回我们的GoogleAuth
对象,我们就会使用它。运行整个函数NgZone
导致了我意想不到的行为。接下来我们使用一个 RxJSBehaviorSubject
isLoaded$
并将值设置为 true。您将在signIn()
和signOut()
函数中看到类似的行为 -获取结果并在 中运行它们NgZone
并更新我们适当的BehaviorSubject
.
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(); } }
现在我们有了我们的服务,是时候使用它了。我们将创建一个用于登录和注销的组件。代码如下:
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 ngOnInit
implementation. 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。