Angular 4 で Connection String や Secret を Environment Variables から取得する

[The twelve-factor app](https://12factor.net/) にある通り、環境依存の情報は、環境変数から設定するようにしたい。しかし、SPA の Angular とかだったらどうするんだろう？きっとこれは基礎的なことなのだけど、意外と自分には難しかったので、今まで学んだことを自分のために、ブログにしておきたい。

# Environment Variables を Angular (4) から使う

ベストプラクティス的なものがないのかなぁ。と色々調べて見たけど、Facebook でなきを入れていたらヒントをもらえた。

<img width="501" alt="Screen Shot 2017-10-18 at 7.20.37 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/be0560b3-6c58-f6a5-83c7-e77d0cc82932.png">

* [Request: Method to pass environment variables during build vs file. #4318](https://github.com/angular/angular-cli/issues/4318)
* [How to pass environment variables?](https://github.com/AngularClass/angular-starter/wiki/How-to-pass-environment-variables%3F)

## 環境変数を取り込む仕組みのあれこれ。

ちなみに、Angular では、環境変数は取り込めない。よくよく考えると、SPA は、ブラウザにダウンロードされて使うのだから、そらそうだ。

上記の記事を読んでいると、どうやら、Angular には、`environment` という仕組みがあるらしい。そのenvironmentを環境によって変えられる様子。

```
src/environments/
├── environment.prod.ts
└── environment.ts
```
ここを、プロジェクトをビルドする時に切り替える。例えばこんな感じ。

_environment.prod.ts 

```ts
export const environment = {
  production: true,
  hostUrl: "http://some.prod"
};
```

_environment.ts

```ts
export const environment = {
  production: false,
  hostUrl: "http://some.dev"
}
```

_app.component.html_ に 下記のコードを入れておく。

``` 
<h2> ServerName: {{hostUrl}}</h2>
```

```
$ ng serve 
```

<img width="886" alt="Screen Shot 2017-10-18 at 7.41.04 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/5b46c8ce-bd80-ae12-04d3-a5f3af3cac9a.png">

だと、デフォルトが起動する。

```
$ ng serve --environment prod
```

<img width="823" alt="Screen Shot 2017-10-18 at 7.41.58 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/fa81bc77-4764-4fbc-0b9e-ac0292af36bf.png">

これでプロダクションが起動した。

## 環境変数を埋め込む

少なくとも [Angular CLI](https://cli.angular.io/)を使っている時はこの方式で行けそう。いきなり本物のプロジェクトに盛り込むより、プロジェクトを生成してみよう。

```
$ ng new host-inject
```

この状態から、環境変数を取り込んで見たい。環境変数は Angular では直接取り込めない。色々読んでいると、ビルド時に、プリコンパイルして盛り込むというのが良さげ。

NOTE: 最初、process.env を直接実行しようとしたけど、これは見つかららないと言われる。ちなみにこれは、`tsconfig.app.json` の下記の部分を追加すると、解消された。

```
"CompilerOptions": {
  typs["node"],
    :
}
```

_scripts/prebuild.ts_ というファイルを作ってみる。 テンプレート _environment.ts.template_ を元に、環境変数を取り込んで作成をするシンプルなもの。

```ts
import * as fs from 'fs';
import * as path from 'path';
import * as ejs from 'ejs';

const environmentFilesDirectory = path.join(__dirname, '../src/environments');
const targetEnvironmentTemplateFileName = 'environment.ts.template';
const targetEnvironemntFileName = 'environment.ts';

const defaultEnvValues = {
    SOME_HOST: "http://default.com"
}

const environmentTemplate = fs.readFileSync(
    path.join(environmentFilesDirectory, targetEnvironmentTemplateFileName),
    {encoding: 'utf-8'}
);
let obj:any = (<any>Object).assign({}, defaultEnvValues, process.env);
const output = ejs.render(environmentTemplate, obj);
fs.writeFileSync(path.join(environmentFilesDirectory, targetEnvironemntFileName), output);
process.exit(0);

```

ちなみに、`(<any>Object).assign` の部分は当初、Object に assign が見つからないと怒られた。`<any>` をつけて解決。

* [Property 'assign' does not exist on type 'ObjectConstructor'](https://stackoverflow.com/questions/35959372/property-assign-does-not-exist-on-type-objectconstructor)

_enviornment.ts.template_ 

```ts
export const environment = {
  production: false,
  hostUrl: "<%= SOME_HOST %>"
};

```

これを実行するために、package.json に、

```
"prebuild": "tsc scripts/prebuild.ts && node scripts/prebuild.js",
```

を追加した。

プリビルドする。

```
$ npm run prebuild
```

しっかり、ファイルがジェネレートされた。

```
$ env
SOME_HOST=http://some.com
```

_environment.ts_

```ts
export const environment = {
  production: false,
  hostUrl: "http://some.com"
};

```

バッチリできている。先ほど紹介してもらった記事を見ても、ジェネレートする方法が一番スッキリきた。

# 本当にこの方式で問題ないのか？

問題は、この方式だと、結局のところ、ブラウザから URL が見れちゃったりするんじゃないの？と思ったが、意外に見えないようだ。もちろん、enviornments の下をうっかり公開する設定にしてないことが大切だとは思うが。よく、SPA を Blob Storage に置くとかの例があるが、ああいうのはどうしているんだろう。Azure Functions Proxy などで制限する感じだろうか。次はその辺をやってみたい。

ちなみに、全てのソースコードはこちらに置いています。

* [TsuyoshiUshio/host-inject](https://github.com/TsuyoshiUshio/host-inject)

# Resource

* [A better way to inject environmental variables in Angular](https://medium.com/@kudresov/a-better-way-to-inject-environmental-variables-in-angular-d3b2d01a3c5e)
* [Request: Method to pass environment variables during build vs file. #4318](https://github.com/angular/angular-cli/issues/4318)
* [How to pass environment variables?](https://github.com/AngularClass/angular-starter/wiki/How-to-pass-environment-variables%3F)