{"id":18655,"date":"2018-03-01T15:00:00","date_gmt":"2018-03-01T15:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/premier_developer\/?p=18655"},"modified":"2020-03-27T10:22:31","modified_gmt":"2020-03-27T17:22:31","slug":"angular-how-to-editable-config-files","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/premier-developer\/angular-how-to-editable-config-files\/","title":{"rendered":"Angular How-to: Editable Config Files"},"content":{"rendered":"<p>In this post, Premier Developer consultant <a href=\"https:\/\/www.linkedin.com\/in\/atkinsonlaurie\/\" target=\"_blank\" rel=\"noopener noreferrer\">Laurie Atkinson<\/a> walks through how to allow editing of your Angular configuration files after your app has been built, bundled, and deployed.<\/p>\n<p>*This post was updated on 12\/3\/2018 to reflect the latest changes to Angular.<\/p>\n<hr align=\"center\" size=\"2\" width=\"100%\" \/>\n<p>The Angular-CLI is the recommended way to build a production-ready app, complete with bundling, uglifying, and tree-shaking. An Angular-CLI generated application even comes with a mechanism for creating environment-specific versions. However, those configuration files are in TypeScript and do not allow editing by IT staff or automated deployment tools such as VSTS. This post provides the steps and code samples for using a JSON configuration file, which can be customized for multiple environments.<i><\/i><\/p>\n<h3>Define TypeScript interface for config settings<\/h3>\n<p>The use of interfaces in an Angular app provides intellisense and type-safety for your entities. For this example, refer to this sample configuration file.<\/p>\n<p><b style=\"font-weight: bold; color: #333333; font-family: 'Segoe UI', Tahoma, Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 14px; font-style: normal; letter-spacing: normal; text-align: start; text-indent: 0px; background-color: #ffffff;\"><span style=\"font-family: Consolas;\">app-config.model.ts<\/span><\/b><\/p>\n<pre class=\"lang:default decode:true \">export interface IAppConfig {\r\n    env: {\r\n        name: string;\r\n    };\r\n    appInsights: {\r\n        instrumentationKey: string;\r\n    };\r\n    logging: {\r\n        console: boolean;\r\n        appInsights: boolean;\r\n    };\r\n    aad: {\r\n        requireAuth: boolean;\r\n        tenant: string;\r\n        clientId: string;\r\n\r\n    };\r\n    apiServer: {\r\n        metadata: string;\r\n        rules: string;\r\n    };\r\n}<\/pre>\n<h3>Create JSON config files<\/h3>\n<p>A convenient place to store configuration files is under the assets folder of your project. Using the interface defined above, sample files could look as follows:<\/p>\n<p><b><span style=\"font-family: Consolas;\">assets\\config\\config.dev.json<\/span><\/b><\/p>\n<pre class=\"lang:default decode:true \">{\r\n    \"env\": {\r\n    \"name\": \"DEV\"\r\n     },\r\n    \"appInsights\": {\r\n    \"instrumentationKey\": \"&lt;dev-guid-here&gt;\"\r\n     },\r\n    \"logging\": {\r\n    \"console\": true,\r\n    \"appInsights\": false\r\n    },\r\n    \"aad\": {\r\n    \"requireAuth\": true,\r\n    \"tenant\": \"&lt;dev-guid-here&gt;\",\r\n    \"clientId\": \"&lt;dev-guid-here&gt;\"\r\n    },\r\n    \"apiServer\": {\r\n    \"metadata\": \"https:\/\/metadata.demo.com\/api\/v1.0\/\",\r\n    \"rules\": \"https:\/\/rules.demo.com\/api\/v1.0\/\"\r\n    }\r\n}<\/pre>\n<p><b><span style=\"font-family: Consolas;\">assets\\config\\config.deploy.json<\/span> <\/b><i>(Note placeholders that are replaced during deployment)<\/i><\/p>\n<pre class=\"lang:default decode:true \">{\r\n    \"env\": {\r\n    \"name\": \"#{envName}\"\r\n    },\r\n    \"appInsights\": {\r\n    \"instrumentationKey\": \"#{appInsightsKey}\"\r\n    },\r\n    \"logging\": {\r\n    \"console\": true,\r\n    \"appInsights\": true\r\n    },\r\n    \"aad\": {\r\n    \"requireAuth\": true,\r\n    \"tenant\": \"#{aadTenant}\",\r\n    \"clientId\": \"#{aadClientId}\"\r\n    },\r\n    \"apiServer\": {\r\n    \"metadata\": \"https:\/\/#{apiServerPrefix}.demo.com\/api\/v1.0\/\",\r\n    \"rule\": \"https:\/\/#{apiServerPrefix}.demo.com\/api\/v1.0\/\",\r\n    }\r\n}<\/pre>\n<h3>Continue to use environment.ts with Angular-CLI build<\/h3>\n<p>The Angular-CLI creates several TypeScript environment files in the environments folder. They will still be used, but contain only the environment name.<\/p>\n<p><b><span style=\"font-family: Consolas;\">environments\\environment.dev.json<\/span><\/b><\/p>\n<pre class=\"lang:default decode:true\">export const environment = {\r\n    name: 'dev'\r\n};<\/pre>\n<p><b><span style=\"font-family: Consolas;\">environments\\environment.deploy.json<\/span><\/b><\/p>\n<pre class=\"lang:default decode:true \">export const environment = {\r\n    name: 'deploy'\r\n};<\/pre>\n<p><b><span style=\"font-family: Consolas;\">angular.json<\/span><\/b><\/p>\n<pre class=\"lang:default decode:true\">\"projects\": {\r\n  \"my-app\": {\r\n    \"architect\": {\r\n      \"build\": {\r\n        \"configurations\": {\r\n          \"deploy\": {\r\n            \"fileReplacements\": [\r\n              {\r\n                \"replace\": \"src\/environments\/environment.ts\",\r\n                \"with\": \"src\/environments\/environment.deploy.ts\"\r\n              }\r\n            ],\r\n            . . .\r\n          }\r\n        }\r\n      },\r\n      \"serve\": {\r\n        . . .\r\n        \"configurations\": {\r\n          \"deploy\": {\r\n            \"browserTarget\": \"my-app:build:deploy\"\r\n          }\r\n<\/pre>\n<h3>Create a service to read config file<\/h3>\n<p>This service will read the correct config file and store the result in a static field in this class..<\/p>\n<p><b><span style=\"font-family: Consolas;\">app.config.ts<\/span> <\/b><i>(Note the use of the interface defined above and config file naming convention to retrieve the appropriate file.)<\/i><\/p>\n<pre class=\"lang:default decode:true\">import { Injectable } from '@angular\/core\u2019;\r\nimport { HttpClient } from '@angular\/common\/http';\r\nimport { environment } from '..\/environments\/environment';\r\nimport { IAppConfig } from '.\/models\/app-config.model';\r\n@Injectable()\r\nexport class AppConfig {\r\n    static settings: IAppConfig;\r\n    constructor(private http: HttpClient) {}\r\n    load() {\r\n        const jsonFile = `assets\/config\/config.${environment.name}.json`;\r\n        return new Promise&lt;void&gt;((resolve, reject) =&gt; {\r\n            this.http.get(jsonFile).toPromise().then((response : IAppConfig) =&gt; {\r\n               AppConfig.settings = &lt;IAppConfig&gt;response;\r\n               resolve();\r\n            }).catch((response: any) =&gt; {\r\n               reject(`Could not load file '${jsonFile}': ${JSON.stringify(response)}`);\r\n            });\r\n        });\r\n    }\r\n}\r\n<\/pre>\n<h3>Load config file prior to app creation<\/h3>\n<p>Angular includes a token named APP_INITIALIZER that allows our app to execute code when the application is initialized. In the app module, use this token to invoke the load method in our config service. Since our method returns a promise, Angular will delay the initialization until the promise is resolved.<\/p>\n<p><b><span style=\"font-family: Consolas;\">app.module.ts<\/span><\/b><\/p>\n<pre class=\"lang:default decode:true\">import { APP_INITIALIZER } from '@angular\/core';\r\nimport { AppConfig } from '.\/app.config';\r\n\r\nexport function initializeApp(appConfig: AppConfig) {\r\n  return () =&gt; appConfig.load();\r\n}\r\n@NgModule({\r\n    imports: [ , , , ],\r\n    declarations: [ . . . ],\r\n    providers: [\r\n       AppConfig,\r\n       { provide: APP_INITIALIZER,\r\n         useFactory: initializeApp,\r\n         deps: [AppConfig], multi: true }\r\n    ],\r\n    bootstrap: [\r\n      AppComponent\r\n    ]\r\n})\r\nexport class AppModule { }<\/pre>\n<h3>Consume the app settings throughout the application<\/h3>\n<p>The config settings are now available from anywhere in the application and they include type-checking provided by the interface.<\/p>\n<pre class=\"lang:default decode:true \">export class DataService {\r\n    protected apiServer = AppConfig.settings.apiServer;\r\n    . . .\r\n    if (AppConfig.settings.aad.requireAuth) { . . . }\r\n}\r\nexport class LoggingService {\r\n    . . .\r\n    instrumentationKey: AppConfig.settings &amp;&amp; AppConfig.settings.appInsights ?\r\n                        AppConfig.settings.appInsights.instrumentationKey : ''\r\n    . . .\r\n    if (AppConfig.settings &amp;&amp; AppConfig.settings.logging) { . . . }\r\n}<\/pre>\n<p>Note: to build a production version of the app using an environment name other than prod, use this command:<\/p>\n<p>ng build &#8211;configuration=deploy<\/p>\n<p>(<a class=\"x-hidden-focus\" href=\"https:\/\/devblogs.microsoft.com\/premier-developer\/como-fazer-arquivos-de-configuracao-editaveis\/\">Link to Portuguese version of this article<\/a>)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post provides the steps and code samples for using a JSON configuration file, which can be customized for multiple environments.<\/p>\n","protected":false},"author":582,"featured_media":37840,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[122,55],"tags":[51,52,3],"class_list":["post-18655","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-angular","category-web","tag-angular","tag-laurie-atkinson","tag-team"],"acf":[],"blog_post_summary":"<p>This post provides the steps and code samples for using a JSON configuration file, which can be customized for multiple environments.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/18655","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/users\/582"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/comments?post=18655"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/18655\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media\/37840"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media?parent=18655"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/categories?post=18655"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/tags?post=18655"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}