Configuring angular production files after build
Asked Answered
C

2

0

I have an angular 9 project which is part of an application suite installer [Wix installer]. One of the settings used by the angular app is the address of API which it fetches its data from some configurable address. I know that I can have many angular environment files and simply use commands such as the followings:

environment.env1.ts

export const environment = {
  production: true,
  apiAddress: "http://apiAddress1/api/",
};


ng build --prod --configuration=env1    

or

environment.env2.ts

    export const environment = {
      production: true,
      apiAddress: "http://apiAddress2/api/",
    };

    ng build --prod --configuration=env2

This means for every potential API address I need to do a new build and run above command. How can I overcome above scenario and configure output binaries after the build?

Assuming there is no clear way of achieving the after-built configuration, can I do 'simple string replace' for the address of API in the generated main*.js files? Would there be any side effects?

Cureall answered 18/5, 2020 at 19:32 Comment(1)
Does that help: https://mcmap.net/q/1330312/-access-environment-variables-in-production-build-angular-4Annabellannabella
M
2

You can outsource your api endpoint in a config file. And provide an AppInitializer to use your config file. Here is an example:

the loader function, which loads the config via http from /config/config.json and sets the api configuration:

export function loadConfig(http: HttpClient, config: ApiConfiguration): (() => Promise<boolean>) {
  return (): Promise<boolean> => {
    return new Promise<boolean>((resolve: (a: boolean) => void): void => {
      http.get('./config/config.json')
        .pipe(
          map((x: any) => {
            config.rootUrl = x.rootUrl + '/v2';
            resolve(true);
          }),
          catchError((x: { status: number }, caught: Observable<void>): ObservableInput<{}> => {
            // 404 local development, other errors are strange
            resolve(x.status === 404);
            return of({});
          })
        ).subscribe();
    });
  };
}

The json contains only one string named rootUrl.

And to use this function, before the app initializes, provide an AppInitializer in your app.module.ts:

providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: loadConfig,
      deps: [
        HttpClient,
        ApiConfiguration
      ],
      multi: true
    }

ApiConfiguration stores the api endpoint which is just a class with a default value (for devlopment):

@Injectable({
  providedIn: 'root',
})
export class ApiConfiguration {
  rootUrl: string = 'localhost:8080';
}

Just put a config file in your /dist folder and it should work :)

Manche answered 18/5, 2020 at 21:33 Comment(2)
This is pretty sweet solution. I had not heard of APP_INITIALIZER before. Instead of wrapping the observable in a Promise, could you have just used toPromise()?Ascites
I'm not sure, but this should also work @DanielGimenezManche
A
1

It is probably possible to perform a find and replace on the generated files after the build completes, but that just feels wrong.

Instead create a configuration.json file and put it in your assets folder. Then in your application, reference the file directly by using an import. You might have to enable this functionality by setting resolveJsonModule to true in your tsconfig.json.

Now, after the application is built, the value can be replaced in configuration.json without fear of unintended side effects by using simple techniques such as in NodeJs with fs.readFileSync(path) and fs.writeFileSync(path, JSON.stringify(data)).

Sample xyz.module.ts section

import dynamicConfig from '../assets/configuration.json';
@NgModule({
  providers: [
    {
      provide: API_BASE_URL,
      useValue: dynamicConfig.apiAddress
    }
  ]
})
export class XyzModule { }
Ascites answered 18/5, 2020 at 20:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.