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


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

Environment Variables を Angular (4) から使う

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

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

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

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

src/environments/
├── environment.prod.ts
└── environment.ts

ここを、プロジェクトをビルドする時に切り替える。例えばこんな感じ。

_environment.prod.ts

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

_environment.ts

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

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

<h2> ServerName: {{hostUrl}}</h2>
$ ng serve 

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

$ ng serve --environment prod

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

環境変数を埋め込む

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

$ ng new host-inject

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

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

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

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

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> をつけて解決。

enviornment.ts.template

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

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

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

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

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

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

Resource