Integrating Google APIs with Angular

Vojtech Mašek
Oct 24, 2019 · 5 min read

💡Motivation

Task: We need to access/display events of a private google calendar.
Problem: You cannot put private calendar into an iframe or query its events using just an API key.
Requirements: Compatible with Angular, TypeScript support(service wrappers/classes and data model types)
Solution: There’s google-api-nodejs-client that provides all we need

Google’s officially supported Node.js client library for accessing Google APIs. Support for authorization and authentication with OAuth 2.0, API Keys and JWT is included.

🛠️Integration

Unfortunately, this library is for NodeJS and there is a slight problem integrating it into “Webpack built apps” and that includes Angular as it relies on some of the NodeJS things that don’t exist in Browser.
That is not going to stop us as there is a workaround (discussed here).

First, we need to extend a Webpack build.

npm i -D @angular-builders/custom-webpack

Then we need to replace builder in angular.json project architect configuration file, we also include a path to a custom Webpack config in options:

"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./extra-webpack.config.js"
},

...
},
...
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
...
},
}

Explanations can be found in Angular’s builder docs.

🔨The “hack”

Now, what is the actual “hack”? We are about to pretend all these things this NodeJS library needs exist in the browser.

  1. Simulate some of the NodeJS runtime internals like global and Buffer
  2. We have to mock a few libs that are required in runtime fs, child_process and https-proxy-agent

Providing fallbacks for global and Buffer

Add the following polyfills under the application imports in your src/polyfills.ts.

import * as process from 'process';
(window as any).process = process;
import { Buffer } from 'buffer';
(window as any).Buffer = Buffer;

Don’t forget to install these packages with npm i -D process buffer .

Put this to your index.html <head> tag. It will get rid of errors related to accessing the global as it substitutes it with the window.

<script>
if (global === undefined) {
var global = window;
}"
</script>

Mocking the libraries

const path = require('path');module.exports = {
resolve: {
extensions: ['.js'],
alias: {
fs: path.resolve(__dirname, 'src/mocks/fs.mock.js'),
child_process: path.resolve(
__dirname,
'src/mocks/child_process.mock.js'
),
'https-proxy-agent': path.resolve(
__dirname,
'src/mocks/https-proxy-agent.mock.js',
),
},
},
};

Explanation of what’s going on would be that we are telling WebPack to replace one import (file) with another. You can notice we put all the “mock” files to src/mock so it is easier to understand what these files are to our colleagues working on a project.

The code inside of these mocks is rather simple. We just need to add a few methods that are used but not required, so they can “do” nothing.
Both fs andchild_process will look like this.

module.exports = {
readFileSync() {},
readFile() {},
};

The https-proxy-agent is even simpler as it can be just
module.exports = {};.

🔑Setting up the access

  1. Create a new GCP project or use an existing one.
  2. At GCP console select your project and go to the Library tab and enable API you want to use (in our case it is Google Calendar and Analytics)
  3. Customize your OAuth consent screen (name, restrictions,…)
    - set all Scopes for Google APIs you need (calendar, analytics, …)
  4. Create OAuth credentials
    - needed if when accessing private data (like calendar events or analytics)
  5. Proceed to Authentication and use the public and private OAuth keys

🔓Authentication

You can access a lot of public data from API easily with an API key. Things like public calendars, but as long as you need some private data (private calendar for example) you will need to authenticate. That can be done via OAuth client.

Provide Google’s OAuth2Client in your app.module.ts providers array. It should look like this:

{
provide: OAuth2Client,
useValue: new OAuth2Client(
// You get this in GCP project credentials
environment.G_API_CLIENT_ID,
environment.G_API_CLIENT_SECRET,
// URL where you'll handle succesful authentication
environment.G_API_REDIRECT,
),
},

We will be using redirect based auth so next thing is generating auth URL.

window.location.href = this.oauth2Client.generateAuthUrl({// 'offline' also gets refresh_token  
access_type: 'offline',
// put any scopes you need there,
scope: [
// in the first example we want to read calendar events
'https://www.googleapis.com/auth/calendar.events.readonly',
'https://www.googleapis.com/auth/calendar.readonly',
// in the second example we read analytics data
'https://www.googleapis.com/auth/analytics.readonly',
],
});

Thanks to refresh_token OAuthClient should be able to handle token exchange even after it is expired so we don’t have to go through google’s Auth screen every hour after token expiration.

️⌨️Example usages

If you like exploring documentation you visit google-apis docs or have a look on Calendar as our it is used in the example below.

📅Using the Calendar Service SDK

Permissions

Make sure that the account you are using does have access to the calendar you want to read events from.

Code Example

Provide Calendar class with the default auth method, in our case it is OAuth. Extend your app.module.ts providers array with this:

{
provide: calendar_v3.Calendar,
useFactory:
(auth: OAuth2Client) => new calendar_v3.Calendar({ auth }),
deps: [OAuth2Client],
},

Now we have access to complete set of Google calendar API features with fully typed SDK interface.

You can get the calendar as you would any other Angular service, have it as a constructor parameter and dependency injection will provide it for you.

constructor(
private calendar: calendar_v3.Calendar,
) {}

Here is an example of how to get a list of events of some specific calendar. We will also filter only events that are “today” and not deleted/cancelled.

this.calendar.events.list({
// required, it is an email, or email like id of a calendar
calendarId: CALENDAR_ID,
// optional, arguments that let you filter/specify wanted events
timeMin: startOfDay(today).toISOString(),
timeMax: endOfDay(today).toISOString(),
showDeleted: false,
singleEvents: true,

}),

You can also perform other tasks such as creating events but don’t forget to require proper scope claims in with your authentication.

📊Using the Analytics SDK

Setting permissions

Analytics Console  Admin  Account column  User Management 
Select the user Activate the "Read & Analyze" checkbox

Getting the “View ID”

Analytics Console  Admin  View column  View Settings 
⮕ Copy the "View ID" number

Code Example

Same as in a previous example, you’ll need a provider. Provide Analytics class with the default auth method. Extend your app.module.ts providers array with this:

{
provide: analytics_v3.Analytics,
useFactory:
(auth: OAuth2Client) => new analytics_v3.Analytics({ auth }),
deps: [OAuth2Client],
},

Again you’ll have it ready to be injected by DI to any injectable class.

constructor(
private analytics: analytics_v3.Analytics,
) {}

This example will get the specified metrics for the desired time range. In this case, we’ll see total page views for the last 30 days.

this.analytics.data.ga.get({
ids: 'ga:xxxxxxxxx', // replace xxxxxxxxx with your view ID
'start-date': '30daysAgo',
'end-date': 'today',
metrics: 'ga:pageviews',
})

📋Conclusion

Having a fully typed SDK for an API is a big difference compared to having to find it all in the docs especially the data models. The Auth part of the problem is also taken care of pretty conveniently so it is not a show stopper for people who don’t know how or don’t want to manage that.
Overall it is a lot easier to create features when you have the project environment and tools set up just right.


Follow me here or on Twitter for further reading or updates. Feel free to reach out in case of some questions.

Angular In Depth

The place where advanced Angular concepts are explained

Vojtech Mašek

Written by

Head of Engineering @ FlowUp.cz ⬆️ Angular & TypeScript enthusiast, Speaker 🔊, Stay at home astronaut 🌍

Angular In Depth

The place where advanced Angular concepts are explained

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade