web-dev-qa-db-ja.com

angular 2 typescriptにgapi.auth2をインポートする

TypeScriptでGoogleのgapi.auth2からいくつかのクラスまたは関数をインポートしようとしました。しかし、以下のコードはタイピングディレクトリにgapi.auth2タイプを正しく追加しても機能しません。

import { GoogleAuth } from 'gapi.auth2';

私はいつもエラーになりました:

Error TS2307: Cannot find module 'gapi.auth2'

'../../typings/gapi.auth2'などの相対ディレクトリ検索を使用しますか?

それとも私がgapiを使っている方法が全く間違っているのでしょうか?

ありがとう!

20
Shu Lin

Angular2でgapiおよび_gapi.auth_を使用するには、NPMを使用してタイプスクリプト定義をインストールします。

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

これにより、 @ types/gapi および @types/gapi.auth2 の2つのパッケージが_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は、ロードしようとしたモジュールが見つかるまで、各_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のNodeモジュール解決メカニズムを使用した場合でも、_.ts_ファイルで変数を宣言して、コンパイル時にAngular2がウィンドウギャップ変数を認識できるようにする必要があります。 _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 )を見ると、関数のみがエクスポートされます。逆に、インターフェースは実装の詳細であるため、エクスポートされずに残り、名前空間外のコードからは見えません。

他のJavaScriptライブラリでの作業TypeScriptのドキュメント で、このすべての作業で何が得られるかを理解することは、読む価値があります。

次に、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
                });
         });
    });
}
_

この関数は自明ではありません、そして正当な理由で...

まず、コールバックだけでなく、構成オブジェクトを使用して gapi.load を呼び出していることに注意してください。 GAPIリファレンス 状態のいずれかを使用できます。

  • ライブラリの読み込みが完了したときに呼び出されるコールバック関数。
  • このメソッドのさまざまな構成パラメーターをカプセル化するオブジェクト。コールバックのみが必要です。

構成オプションを使用すると、ライブラリのタイムアウト時のエラー、またはエラーのみをrejectすることができます。私の経験では、ライブラリのロードは初期化よりも頻繁に失敗します。そのため、構成オブジェクトは単なるコールバックよりも優れています。

次に、_gapi.load_をラップします

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

NgZone.runはドキュメント化されています および状態

_zone.run_を介して関数を実行すると、Angularゾーン[...]の外部で実行されたタスクからAngularゾーンに再度入ることができます

_gapi.load_の呼び出しがAngularゾーンを離れるので、これはまさに私たちが望むことです。これを省略すると、非常にファンキーな結果が残り、デバッグが困難になる可能性があります。

3番目に、loadClient()は解決されたpromiseを返します-呼び出し元が_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クライアントを初期化する必要がありますs):

_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ゾーンに再度入るようにしています。

実際には、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>
_

asyncordefer属性を使用することはできません。いずれの場合も、gapiがロードされる前にAngular 2の世界に入るからです。

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

以前に、 gapiライブラリ のローカルの縮小されたコピーを_/main-app/src/assests_フォルダーに読み込んでインポートすることで、ページの読み込み速度を高速に保つことを提案しました:

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

ただし、私は強く推奨しないこれを推奨します。 Googleが https://apis.google.com/js/api.js を更新する可能性があり、クライアントが機能しなくなります。私はこれに2回巻き込まれました。結局のところ、_//apis.google.com/js/_からインポートし、それをブロッキング呼び出しとして保持することをお勧めします。

46
Jack

これは、RxJSライブラリを使用するように @ Jack's の回答から変更されています。元の質問では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_に入れました。 @ Jack を推奨するため、asyncdeferを省略しました。

    _<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 Introduction または their documentation を確認してください。 GoogleAuthオブジェクトを取得したら、_this.zone.run_を使用していることに注意してください。 NgZoneで関数全体を実行すると、予期しない動作が発生しました。次に、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を設定するのに役立つことを願っています。

10
StephenSolace