Deploy Angular Universal to Multiple Environments Using Node ENV Vars
Tags: angular
Published: 2021-07-30 17:38:20

Deploying Angular and especially Angular Universal using CI to a staging and a production environment is very difficult.  A quick Google search reveals a lot of really ugly solutions.  Here is a cleaner solution:

  
The root of the issue is how the Angular CLI decides which angular environment to use based on a command line variable.  

`ng build --configuration=production` Will build the application with the production environment variables set in `environments/environment.prod.ts`. How do you get a production Heroku server to build with `ng build --configuration=production` and a staging server to build with `ng build --configuration=staging`?  

Heroku now automatically calls `npm install` and `npm build`.  Have your npm build script call: `node build-app.js`

```
const { exec } = require('child_process');
console.info(
    'node env var ANGULAR_ENVIRONMENT_CONFIGURATION',
    process.env.ANGULAR_ENVIRONMENT_CONFIGURATION
);

let buildScript = 'npm run build:staging:ssr';
if (process.env.ANGULAR_ENVIRONMENT_CONFIGURATION === 'production') {
    buildScript = 'npm run build:prod:ssr';
}
const child = exec(buildScript, function (err, stdout, stderr) {
    if (err) throw err;
    else console.info(stdout);
});

child.stdout.on('data', function (data) {
    process.stdout.write(data);
});

child.stderr.on('data', function (data) {
    process.stdout.write(data);
});

child.on('exit', function (data) {
    process.stdout.write("I'm done!");
});
```

The idea here is that this node app is able to read node environment variables and then decide on which angular build command to call.

In Heroku, set the node environment variable `ANGULAR_ENVIRONMENT_CONFIGURATION` to either `staging` or `production` appropriately.


This is pretty much all you need to know for deploying Angular. Take a look below at the npm build scripts and simply omit the `:ssr` and `ng run my-app:server:production`. There are plenty of other tutorials out there that demonstrate how to add `staging` to the angular.json file.  Here are a few:
https://tattoocoder.com/angular-cli-using-the-environment-option/
https://blog.angulartraining.com/how-to-manage-different-environments-with-angular-cli-883c26e99d15

### Angular Universal
If you are using *Angular Universal* it will always default to building the pre-render files with the production environment.  You have to add some additional configuration in order to get it to pre-render with the staging environment.  The key is to setup your npm build scripts like so:

```
"build:prod:ssr": "ng build --configuration=production && ng run my-app:server:production",
"build:staging:ssr": "ng build --configuration=staging && ng run my-app:server:staging",
```

And update your angular.json
```
 "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "dist/my-app/server",
            "main": "server.ts",
            "tsConfig": "tsconfig.server.json"
          },
          "configurations": {
            "production": {
              "outputHashing": "media",
              "sourceMap": false,
              "optimization": true
            },
            "staging": {
              "outputHashing": "media",
              "sourceMap": false,
              "optimization": true,
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.staging.ts"
                }
              ]
            }
          }
        },
"serve-ssr": {
          "builder": "@nguniversal/builders:ssr-dev-server",
          "options": {
            "browserTarget": "my-app:build",
            "serverTarget": "my-app:server"
          },
          "configurations": {
            "production": {
              "browserTarget": "my-app:build:production",
              "serverTarget": "my-app:server:production"
            },
            "staging": {
              "browserTarget": "my-app:build:staging",
              "serverTarget": "my-app:server:staging"
            }
          }
        },
        "prerender": {
          "builder": "@nguniversal/builders:prerender",
          "options": {
            "routes": [
              "/"
            ]
          },
          "configurations": {
            "production": {
              "browserTarget": "my-app:build:production",
              "serverTarget": "my-app:server:production"
            },
            "staging": {
              "browserTarget": "my-app:build:staging",
              "serverTarget": "my-app:server:staging"
            }
          }
        }
```