Integrating dotenv and Google Maps API into your Angular project
Tags: angular, maps, netlify, webdev
Published: 2020-10-04 08:53:35

When you store your Angular project in a public git repo, oftentimes you don’t want to expose your API keys, such as Google Maps API key,  to everyone. Although that doesn’t bring you any security issues, as the keys can be seen by everyone through the dev tools anyway, there are the reasons you still don’t want to store them in the repo. For example, when people want to use their API keys instead, they will have to search through your code to replace the keys. But with the keys stored in the `.env` file, they can just copy the `.env.example` file and insert the new values.

Files in the `environment` folder do not fit this purpose, as they are usually tracked by Git. So to hide our keys and make them easy to replace, we can use the magic of the `.env` file. 

## Table of contents
* [Prerequisites](#prerequisites)
* [dotenv integration](#dotenv-integration)
* [Google Maps API integration](#google-maps-api-integration)
* [Using multiple environments](#using-multiple-environments)
* [Using Netlify environment variables](#using-netlify-environment-variables)

## Prerequisites

In this example, we are going to integrate the Google Maps API and store the API keys in the `.env` file.

Firstly, load the necessary npm packages.

`npm i -D @angular-builders/custom-webpack @types/node @types/googlemaps dotenv-webpack`

Install the official Google Maps API component for Angular:

`npm i @angular/google-maps`

## dotenv integration

Create webpack configs for development and production environments:

#### **`/webpack-dev.config.js`**
```javascript
const Dotenv = require("dotenv-webpack");

module.exports = {
  plugins: [
    new Dotenv(),
  ],
};
```

Set the `systemvars` key if you plan to use [Netlify environment variables](#using-netlify-environment-variables) in production. You can see all the available options on [dotenv-webpack npm page](https://www.npmjs.com/package/dotenv-webpack).

#### **`/webpack-prod.config.js`**
```javascript
const Dotenv = require("dotenv-webpack");

module.exports = {
  plugins: [
    new Dotenv({
      systemvars: true,
    }),
  ],
};
```
Then open `angular.json` file and modify the `build` target as follows:
#### **`/angular.json`**
```json
"architect": {
    "build": {
      "builder": "@angular-builders/custom-webpack:browser",
      "options": {
        "customWebpackConfig": {
            "path": "./webpack-dev.config.js"
            },
...
      "configurations": {
        "production": {
            "customWebpackConfig": {
                "path": "webpack-prod.config.js"
            },
          "fileReplacements": [
...
```

Modify the `serve` target so the values from `.env` file were also accesible during the development process with the `serve` command:

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

Open `tsconfig.app.json` and the types for nodejs:
#### **`/tsconfig.app.json`**
```json
"compilerOptions": {
    "types": ["node"]
},
```

Now create `.env.example` and `.env`. Don't forget to add `.env` to `.gitignore` file

#### **`/.env.example`**
```
# Google maps key
GOOGLE_MAPS_KEY=
```

Insert your [actual Google Maps key](https://developers.google.com/maps/documentation/javascript/get-api-key) into `.env` file.

#### **`/.env`**
```
GOOGLE_MAPS_KEY=your key
```

#### **`/.gitignore`**
```
/.env
```

Duplicate the variables into the `environment` files.

#### **`/src/environments/environment.ts`**
```typescript
export const environment = {
  production: false,
  googleMapsKey: process.env.GOOGLE_MAPS_KEY,
};
```

#### **`/src/environments/environment.prod.ts`**
```typescript
export const environment = {
  production: true,
  googleMapsKey: process.env.GOOGLE_MAPS_KEY,
};
```

## Integrating Google Maps API

> Note that the method below with jsonp only makes sense if you have a component with the map constantly residing on your page i.e. in the footer. If you have it on a dedicated page please note that google maps script will be loaded each time the page is loaded!

Modify your `app.module.ts` (or any module where you declare the component that will use Google Maps) as follows:
#### **`/src/app/app.module.ts`**
```typescript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { GoogleMapsModule } from '@angular/google-maps';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { HttpClientJsonpModule, HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,

   // Import http and jsonp modules
   // as well as the google maps module
    HttpClientModule,
    HttpClientJsonpModule,
    GoogleMapsModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

The component which will use Google Maps will look like this:
#### **`/src/app/app.component.html`**
```typescript
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { Observable, of } from 'rxjs';
import { environment } from 'src/environments/environment';
import { map, catchError } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
  // Some maps options
  mapOptions: google.maps.MapOptions = {
    zoom: 15,
    center: {
      lat: 35.3606255,
      lng: 138.7273634,
    },
    mapTypeId: 'hybrid',
  };
  mapMarker: google.maps.MarkerOptions = {
    position: {
      lat: 35.3606255,
      lng: 138.7273634,
    },
    label: {
      color: 'red',
      text: 'Yep',
    },
    title: 'Yep',
  };

  // This observable will emit the value when the Maps API is loaded
  // This is needed to make sure we don't access the API when it's not loaded yet
  mapsLoaded: Observable<boolean>;

  constructor(httpClient: HttpClient) {
    this.mapsLoaded = httpClient
      .jsonp(
        `https://maps.googleapis.com/maps/api/js?key=${environment.googleMapsKey}`,
        'callback'
      )
      .pipe(
        map(() => true),
        catchError(() => of(false))
      );
  }

  ngOnInit() {}
}
```

And the template file will look like this:
#### **`/src/app/app.component.html`**
```html
<div *ngIf="mapsLoaded | async">
    <google-map [zoom]="mapOptions.zoom"
        [center]="mapOptions.center"
        [mapTypeId]="mapOptions.mapTypeId">
        <map-marker [position]="mapMarker.position"
            [label]="mapMarker.label"
            [title]="mapMarker.title">
        </map-marker>
    </google-map>
</div>
```

## Using multiple environments

Find instructions below if you want to have separate configs for development, production, and staging environments.

Firstly, create a file for each of your environments accordingly, i.e. `.env.production`, `.env.development` and `.env.staging`.

Now you can put the commands into `package.json` that will look something like this:

#### **`/package.json`**
```json
  "scripts": {
    "start:prod": "APP_ENV=production ng serve",
    "start:dev": "APP_ENV=development ng serve",
    "start:staging": "APP_ENV=staging ng serve",

    "build:prod": "APP_ENV=production ng build --prod",
    "build:dev": "APP_ENV=development ng build --prod",
    "build:staging": "APP_ENV=staging ng build --prod"
  },
```
Now you can just write function that will check the `process.env.APP_ENV` in webpack config file and return the right config:

#### **`/webpack-dev.config.js`**
```javascript
const Dotenv = require("dotenv-webpack");

function getConfig() {
  if (process.env.APP_ENV === 'production') {
    return '.env.production'
  } else if (process.env.APP_ENV === 'development') {
    return '.env.development'
  } else {
    return '.env.staging'
  }
}

module.exports = {
  plugins: [
    new Dotenv({
      path: getConfig()
    }),
  ],
};
```

## Using Netlify environment variables
Because we do not store `.env` files in git repositories, Netlify cannot read them and emit them into the `process.env` variables. Fortunately, we have [Netlify environment variables](https://docs.netlify.com/configure-builds/environment-variables/#sensitive-variable-policy) which will be emitted into `process.env` during the build time of your application.

You can set the environment variables in Build & deploy section of your site settings.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9q2rywy89kuee8k6lcou.png)

The important thing is to not forget to include the `systemvars` key into your dotenv config so that can work.

```javascript
const Dotenv = require("dotenv-webpack");

module.exports = {
  plugins: [
    new Dotenv({
      systemvars: true,
    }),
  ],
};
```