web-dev-qa-db-fra.com

Importez gapi.auth2 dans angular 2 tapuscript

J'ai essayé d'importer certaines classes ou fonctions de Google gapi.auth2 en TypeScript. Mais le code ci-dessous ne fonctionne jamais même si j'ai correctement ajouté les types gapi.auth2 dans le répertoire de typage.

import { GoogleAuth } from 'gapi.auth2';

J'ai toujours eu une erreur:

Error TS2307: Cannot find module 'gapi.auth2'

Dois-je utiliser une recherche de répertoire relative, comme "../../typings/gapi.auth2"?

Ou peut-être que la façon dont j'utilise le gapi est totalement fausse?

Merci!

20
Shu Lin

Pour utiliser gapi et gapi.auth Avec Angular2, installez les définitions de script de type à l'aide de NPM.

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

Cela va installer deux packages, @ types/gapi et @ types/gapi.auth2 dans le dossier node_modules Et enregistrer la configuration dans package.json.

Inspectez votre dossier node_modules Pour vérifier qu'il s'installe correctement. Si votre application Angular2 est appelée application principale, vous devriez voir:

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

Modifiez tsconfig.json Pour inclure les nouveaux types gapi et gapi.auth2 (Ci-dessous n'est qu'un extrait):

{
  "compileOnSave": false,
  "compilerOptions": {
    "types": ["gapi", "gapi.auth2"]
  }
}

À ce stade, je recommande fortement de prendre un café et de lire Résolution du module TypeScript , vous pouvez passer directement à Comment Node.js résout les modules :

La résolution [...] d'un nom de module non relatif est effectuée différemment. Node recherchera vos modules dans des dossiers spéciaux nommés node_modules. Un dossier node_modules Peut être au même niveau que le fichier actuel, ou plus haut dans le chaîne de répertoires. Node parcourra la chaîne de répertoires en parcourant chaque node_modules jusqu'à ce qu'il trouve le module que vous avez essayé de charger.

Pour cette raison, vous ne devriez pas avoir besoin d'ajouter une référence aux définitions de type dans votre service ou composant Angular2 (ou partout où vous utilisez gapi ou gapi.auth2).

Cependant, si vous ajoutez une référence aux définitions TypeScript gapi ou gapi.auth2, Elle doit référencer le fichier .ts Installé à l'aide de npm install (Notez que vous devez conservez le /// sinon vous obtiendrez une erreur):

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

Le chemin est relatif, le vôtre peut donc différer selon l'emplacement de votre fichier .ts Par rapport à l'emplacement d'installation des définitions TypeScript.

Que vous ayez ajouté une référence explicite ou utilisé le mécanisme de résolution du module Node de TypeScript), vous devez toujours déclarer vos variables dans votre fichier .ts Pour qu'Angular2 connaisse la variable window gapi au moment de la compilation. . Ajoutez declare var gapi: any; À votre fichier .ts Mais faites pas placez-le dans une définition de classe. Je mets le mien juste en dessous de toutes les importations:

// 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;

En regardant les définitions elles-mêmes ( https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/gapi/index.d.ts ), seules les fonctions sont exportées. Inversement, les interfaces sont des détails d'implémentation, elles ne sont donc pas exportées et ne seront pas visibles pour le code en dehors de l'espace de noms.

Travailler avec d'autres bibliothèques JavaScript dans documentation TypeScript vaut la peine d'être lu pour comprendre ce que nous obtenons avec tout ce travail.

Ensuite, chargez le client gapi avec votre propre fonction (possible dans un service 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
                });
         });
    });
}

Cette fonction n'est pas triviale, et pour cause ...

Tout d'abord, notez que nous appelons gapi.load avec un objet de configuration et pas seulement un rappel. Les états référence GAPI peuvent être utilisés:

  • Une fonction de rappel qui est appelée lorsque les bibliothèques ont terminé le chargement.
  • Un objet encapsulant les différents paramètres de configuration de cette méthode. Seul un rappel est requis.

L'utilisation d'une option de configuration nous permet de rejeter la promesse lors du chargement du délai d'expiration de la bibliothèque, ou simplement des erreurs. D'après mon expérience, le chargement de la bibliothèque échoue plus souvent que son initialisation - c'est pourquoi l'objet de configuration est meilleur qu'un simple rappel.

Deuxièmement, nous enveloppons gapi.load Dans

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

NgZone.run est documenté et déclare

L'exécution de fonctions via zone.run Vous permet de ressaisir Angular à partir d'une tâche exécutée en dehors de la zone Angular [...]

C'est exactement ce que nous voulons puisque l'appel à gapi.load Quitte la zone Angular. L'omission de cela peut laisser des résultats très funky qui peuvent être difficiles à déboguer.

Troisièmement, loadClient() renvoie une promesse qui est résolue - permettant à l'appelant de choisir comment il gère gapi.load. Par exemple, si notre méthode loadClient appartenait à un service Angular, apiLoaderServce, un composant peut utiliser ngOnInit pour charger gapi:

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

Une fois gapi.load Appelé, gapi.client Sera prêt et vous devrez l'utiliser pour initialiser le client JavaScript avec votre clé API, OAuth ID client, étendue, et document (s) de découverte d'API:

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

Remarquez que notre ami NgZone.run est à nouveau utilisé pour s'assurer que la zone Angular est à nouveau saisie.

En pratique, j'ajoute loadClient() et initClient() à un service Angular. Dans un composant de haut niveau Angular (généralement juste en dessous du composant d'application) Je charge et initialise dans 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;
    });
}

Enfin, vous devez ajouter le fichier de script gapi à votre fichier.

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

Vous ne devez pas utiliser les attributs async ou defer car l'un ou l'autre provoquera Angular = 2 monde à entrer avant le chargement de gapi.

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

J'avais précédemment suggéré de maintenir des vitesses de chargement de page rapides en chargeant une copie locale et réduite du bibliothèque gapi dans le dossier /main-app/src/assests Et en important:

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

Cependant, je fortement recommande de ne pas faire cela. Google peut mettre à jour https://apis.google.com/js/api.js et votre client se cassera. J'ai été pris par deux fois. En fin de compte, il valait mieux juste importer à partir de //apis.google.com/js/ Et le garder comme un appel de blocage.

46
Jack

Ceci est modifié de @ Jack's answer pour utiliser la bibliothèque RxJS. Alors que la question d'origine demande Angular 2, j'utilise Angular 5 ici au cas où quelqu'un travaillerait avec une version mise à jour).

  1. La première étape est la même, en téléchargeant les types de gapi avec npm.

    npm install --save @types/gapi
    npm install --save @types/gapi.auth2
    
  2. Vous devrez mettre à jour votre tsconfig.json. Si vous rencontrez des problèmes, vous devrez peut-être également mettre à jour tsconfig.app.json et tsconfig.spec.json. Ils héritent de tsconfig.json, mais si vous spécifiez des types, je pense qu'ils peuvent écraser la base. Extrait ci-dessous:

    "typeRoots": [
      "node_modules/@types"
    ],
    "types": [
      "gapi",
      "gapi.auth2"
    ],
    "lib": [
      "es2017",
      "dom"
    ]
    
  3. Ajoutez une référence au platform.js De Google. J'ai mis le mien dans index.html. J'ai omis async et defer comme @ Jack recommandé.

    <script src="https://apis.google.com/js/platform.js"></script>
    
  4. Créez ensuite un service d'authentification. Le code complet est ici:

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

Il se passe beaucoup de choses ici. Commencez par prendre connaissance des RxJS BehaviorSubjects. Nous les utiliserons pour notifier nos composants des changements. Notre fonction loadAuth2 Utilise la bibliothèque de Google pour obtenir un objet gapi.auth2.GoogleAuth. Si vous avez besoin de plus d'informations sur la bibliothèque d'authentification de Google, veuillez consulter leur introduction ou leur documentation . Notez que nous utilisons this.zone.run Une fois que nous avons récupéré notre objet GoogleAuth. L'exécution de la fonction entière dans un NgZone a conduit à un comportement inattendu pour moi. Ensuite, nous prenons un RxJS BehaviorSubjectisLoaded$ Et définissez la valeur sur true. Vous verrez un comportement similaire dans les fonctions signIn() et signOut(), en prenant les résultats et en les exécutant dans un NgZone et en mettant à jour notre BehaviorSubject approprié.

  1. Maintenant que nous avons notre service, il est temps de l'utiliser. Nous allons créer un composant pour vous connecter et vous déconnecter. Le code est ci-dessous:

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

La partie la plus importante ici est l'implémentation de ngOnInit. C'est là que nous nous abonnerons aux modifications de AuthenticatorService et mettrons à jour la vue en conséquence.

J'espère que ces étapes aideront quelqu'un là-bas à configurer gapi.auth2 dans son projet.

10
StephenSolace