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.
import { GoogleAuth } from 'gapi.auth2';
I always got error:
Error TS2307: Cannot find module 'gapi.auth2'
Shall I use some relative directory searching, such as '../../typings/gapi.auth2'?
Or maybe the way I am using the gapi is totally wrong?
Thanks!
The compiler will attempt to locate an ambient module declaration. So you should install:
https://github.com/retyped/gapi.auth2-tsd-ambient
To use
gapi
andgapi.auth
with Angular2, install the type script definitions using NPM.This will install two packages, @types/gapi and @types/gapi.auth2 to the
node_modules
folder and save the configuration inpackage.json
.Inspect your
node_modules
folder to check they install correctly. If your Angular2 app is called main-app, you should see:Edit
tsconfig.json
to include newgapi
andgapi.auth2
types (below is just a excerpt):At this point I highly recommend grabbing a coffee and reading Typescript Module Resolution, you can skip straight to How Node.js resolves 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
orgapi.auth2
).However, if you do add a reference to the
gapi
orgapi.auth2
TypeScript definitions, it must reference the.ts
file installed usingnpm install
(note, you must keep the///
oherwise you'll get an error):The path is relative, so yours may differ depending on where your
.ts
file is relative to where you installed the TypeScript definitions.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. Adddeclare var gapi: any;
to your.ts
file but do not place it within a class definition. I put mine just below any imports: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.
Working with Other JavaScript Libraries in TypeScript documentation is worth reading to understand what we're getting with all this work.
Next, load the
gapi
client with your own function (possible in an Angular Service):This function is non-trivial, and for good reason...
Firstly, note we're calling gapi.load with a configuration object and not just a callback. The GAPI reference states either can be used:
Using a configuration option allows us to reject the 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.
Secondly, we're wrapping
gapi.load
inNgZone.run is documented and states
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.Thirdly,
loadClient()
returns a promise that is resolved - allowing the caller to choose how they handlegapi.load
. For example if ourloadClient
method belonged to an Angular service,apiLoaderServce
, a component may usengOnInit
to loadgapi
: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):Notice our friend NgZone.run is used once again to ensure the Angular Zone is re-entered.
In practice, I add
loadClient()
andinitClient()
to an Angular Service. In a high-level Angular component (usually just below the app-component) I load and initialize inngOnInit
:Lastly, you need to add the gapi script file to your file.
You must not use the
async
ordefer
attributes since either will cause Angular 2 world to enter before gapi has loaded.I had previously suggested keeping page-load speeds fast by loading a local, minified copy of the gapi library in the
/main-app/src/assests
folder and importing:However, I strongly recommend not doing this. Google may update https://apis.google.com/js/api.js and 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.This is modified from @Jack's answer 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.
The first step is the same, downloading the gapi types with npm.
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:
Add a reference to Google's
platform.js
. I put mine inindex.html
. I left outasync
anddefer
as @Jack recommended.Next create an authentication service. The complete code is here:
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 agapi.auth2.GoogleAuth
object. If you need more information on Google's authentication library please check out their introduction or their documentation. Note we're usingthis.zone.run
once we get ourGoogleAuth
object back. Running the entire function in anNgZone
led to unexpected behavior for me. Next we take an RxJSBehaviorSubject
isLoaded$
and set the value to true. You'll see similar behavior in thesignIn()
andsignOut()
functions- taking the results and running them in anNgZone
and updating our appropriateBehaviorSubject
.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:
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.Hope these steps help someone out there to set up gapi.auth2 in their project.