{"id":1998,"date":"2017-06-06T17:58:03","date_gmt":"2017-06-06T17:58:03","guid":{"rendered":"http:\/\/salientsoft.co.uk\/?p=1998"},"modified":"2018-10-10T17:15:56","modified_gmt":"2018-10-10T17:15:56","slug":"ionicangularexternalising-application-configuration","status":"publish","type":"post","link":"https:\/\/salientsoft.co.uk\/?p=1998","title":{"rendered":"Angular\/Ionic&ndash;externalising application configuration"},"content":{"rendered":"<h3>Key Goals<\/h3>\n<ol>\n<li>The primary goal is to completely decouple configuration data from the build deployment. This is a key requirement in an enterprise setting, not least because it is vital that the very same build can be tested and signed off in a number of different development and test environments pointing at different server infrastructure.\u00a0 If a different build was needed for each environment, there is always the risk of an incorrect build, so technically the final production build has never been tested and could break. A good discussion on this may be found on <a href=\"https:\/\/www.jvandemo.com\/how-to-configure-your-angularjs-application-using-environment-variables\/\">The Twelve Factor App site here<\/a>.<\/li>\n<li>Upon investigating a pattern for this in Ionic and Angular, I found numerous posts citing different ways around the problem, some appearing quite complex. I was left feeling that this need was not something that had been baked in to the Framework design as a first class citizen \u2013 it was certainly not an up front and clear pattern in the documentation. Therefore I wanted a reusable pattern to use as a standard for all my Angular development.<\/li>\n<li>I am using angular-cli with webpack, which is the recommended approach. This produces simple static deployment bundles which are web server agnostic \u2013 just an html file which loads one or more js bundles, plus assets such as fonts and images. The pattern for externalising configuration aims to fit in with this and makes no assumptions about the ability to build configuration data dynamically server-side or inject configuration related information into the web server. For example, a common pattern when using Apache Tomcat with Java applications is to inject a configuration location as a JVM parameter into tomcat, which is used later server-side to access the configuration dynamically. We make no assumptions here about the ability to do this kind of trick \u2013 any such mechanisms (discussed further below) would be an extension to the pattern for a particular server.<\/li>\n<li>An important goal of the pattern is that it should be testable, i.e. we use dependency injection to inject configuration into the classes that use it, (in our case via a service). This allows unit tests to inject test configuration as required.<\/li>\n<li>The pattern also supports offline use e.g. in an Ionic application. If, as some solutions advocate, the configuration was loaded via an HTTP service in javascript code, this would not work offline with Ionic. The challenge that remains is how and where the configuration would be located in an Ionic deployment, but that is a question for another day and further invesigation.<\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<h3>Solution Overview<\/h3>\n<ol>\n<li>After a fair amount of investigation I settled on a fairly simple pattern as per this post <a href=\"http:\/\/www.jvandemo.com\/how-to-configure-your-angularjs-application-using-environment-variables\/\">here<\/a>. <a href=\"http:\/\/stackoverflow.com\/a\/27716804\">This stackoverflow comment<\/a> refers to the same technique.\u00a0 I adapted the technique for my situation \u2013 the key points are as follows, and a code sample is shown below.<\/li>\n<li>The basic idea is to load configuration data as a Json object literal from a .js file. This is done by a <em>&lt;script&gt;<\/em> tag in index.html, prior to the loading of the webpack bundles, which loads the configuration typically as a file from a known location, where the <em>src<\/em> of the <em>&lt;script&gt;<\/em> tag is deemed not part of the build.<\/li>\n<li>Whilst this would typically be a location on the same server such as the root directory where index.html is located, it does not have to be, and could equally well be loaded from another server over http. The key point is that the precise <em>src<\/em> for the script tag is a contract between index.html and any deployment\/configuration management software. For example, the configuration file might be dynamically created by Ansible or similar, or the configuration might not be a file at all, and could be dynamically created server side via a web service. These possibilities are all extensions to the basic pattern and depend on the particular web servers and deployment techniques in play.<\/li>\n<li>When running in development mode using ng serve, the bundles are created in memory and no files exist. Therefore this mechanism will not work as the <em>&lt;script&gt;<\/em> tag will not load anything. We therefore provide a means of having default configuration in the code which will be overridden by the above configuration file if present. If the configuration file is not present (ng serve in dev mode) you will get a 404 for the script tag on the console, which can be ignored.<\/li>\n<li>When running in development mode using files (e.g. via\u00a0<a href=\"https:\/\/www.npmjs.com\/package\/lite-server\">lite-server<\/a>\u00a0using\u00a0\u00a0<em>ng build &#8211;watch<\/em> and running lite-server from the <em>dist<\/em> subdirectory), a config file can be used in the normal way to override the default config if desired.<\/li>\n<li>The default configuration object is exported as a constant from the appropriate environment file, e.g. environment.ts. Whilst there could be default settings in environment.prod.ts, the default is typically set to null in this case so that a configuration file is always used.<\/li>\n<li>The structure of the configuration data is typed via an AppConfig interface which is used everywhere the data is loaded or referenced. In simple cases the configuration data may be just properties of the root AppConfig object. However in more complex cases the object graph could be extensive.<\/li>\n<li>The configuration file loads the configuration object into a global variable which is then read by the AppConfigService, and if present overrides the default configuration. Object.freeze() is used (along with readonly properties on the AppConfig interface) to make the configuration data immutable. Whilst in theory it may be possible to inject the resulting configuration as a constant using a value provider, when I tried this (with the latest Angular 4) I hit a variety of intermittent compile\/build errors citing <em>ERROR in Error encountered resolving symbol values statically<\/em>. In the end I went with an injected configuration service, which consuming classes set up as a getter to allow convenient access.<\/li>\n<li>The AppConfigService loads the global configuration variable via a casted reference to the window object. I tried to get this to work via a declare to reference it as an external object but again I hit compile errors when doing so.<\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<h3>Example Code Listings<\/h3>\n<p>&nbsp;<\/p>\n<p><strong><u>index.html<\/u><\/strong><\/p>\n<blockquote>\n<pre>&lt;!doctype html&gt;\r\n&lt;html&gt;\r\n&lt;head&gt;\r\n  &lt;meta charset=\"utf-8\"&gt;\r\n  &lt;title&gt;Places Admin&lt;\/title&gt;<\/pre>\n<p>&lt;base href=&#8221;\/&#8221;&gt;<\/p>\n<p>&lt;meta name=&#8221;viewport&#8221; content=&#8221;width=device-width, initial-scale=1&#8243;&gt;<br \/>\n&lt;link rel=&#8221;icon&#8221; type=&#8221;image\/x-icon&#8221; href=&#8221;favicon.ico&#8221;&gt;<\/p>\n<p>&lt;script type=&#8221;text\/javascript&#8221; src=&#8221;appConfig.js&#8221;&gt;&lt;\/script&gt;<\/p>\n<p>&lt;\/head&gt;<br \/>\n&lt;body&gt;<br \/>\n&lt;app-root&gt;Loading&#8230;&lt;\/app-root&gt;<br \/>\n&lt;\/body&gt;<br \/>\n&lt;\/html&gt;<\/p><\/blockquote>\n<p><strong><u>appConfig.js \u2013 external configuration file.<\/u><\/strong><\/p>\n<blockquote>\n<pre>\/\/ Application Configuration\r\nvar salientSoft_appConfig = {\r\n  appTitle: \"The Places Guide\",\r\n  apiBase: \"http:\/\/localhost:5984\/places-guide\/\",\r\n  messageTimeout: number = 3000\r\n};<\/pre>\n<\/blockquote>\n<p><strong><u>environment.ts<\/u><\/strong><\/p>\n<blockquote>\n<pre>\/\/ The file contents for the current environment will overwrite these during build.\r\n\/\/ The build system defaults to the dev environment which uses `environment.ts`, but if you do\r\n\/\/ `ng build --env=prod` then `environment.prod.ts` will be used instead.\r\n\/\/ The list of which env maps to which file can be found in `.angular-cli.json`.<\/pre>\n<p>import {AppConfig} from &#8216;..\/config\/app-config&#8217;;<\/p>\n<p>export const environment = {<br \/>\nproduction: false,<br \/>\n};<\/p>\n<p>export const defaultAppConfig: AppConfig = {<br \/>\nappTitle: &#8216;The Places Guide&#8217;,<br \/>\napiBase: &#8216;http:\/\/localhost:5984\/places-guide\/&#8217;,<br \/>\nmessageTimeout: 3000<br \/>\n};<\/p><\/blockquote>\n<p><strong><u>environment.prod.ts<\/u><\/strong><\/p>\n<blockquote>\n<pre>import {AppConfig} from '..\/config\/app-config';<\/pre>\n<p>export const environment = {<br \/>\nproduction: true<br \/>\n};<\/p>\n<p>export const defaultAppConfig: AppConfig = null;<\/p><\/blockquote>\n<p><strong><u>appConfig.ts \u2013 config file interface<\/u><\/strong><\/p>\n<blockquote>\n<pre>\/* Root application configuration object\r\n   This encapsulates the global config json properties.\r\n   These must match what is in the actual config file, normally \/appConfig.js.\r\n *\/\r\nexport interface AppConfig {\r\n    readonly appTitle: string;\r\n    readonly apiBase: string;\r\n    readonly messageTimeout: number;\r\n}<\/pre>\n<\/blockquote>\n<p><strong><u>app-config.service.ts<\/u><\/strong><\/p>\n<blockquote>\n<pre>import {Injectable} from '@angular\/core';\r\nimport {AppConfig} from '.\/app-config';\r\nimport {defaultAppConfig} from '..\/environments\/environment';<\/pre>\n<p>@Injectable()<br \/>\nexport class AppConfigService {<br \/>\nprivate _appConfig: AppConfig;<br \/>\nbuildConfig(): AppConfig {<br \/>\nconst appConfig: AppConfig = (&lt;any&gt;window).salientSoft_appConfig || defaultAppConfig;<br \/>\nreturn Object.freeze(appConfig);<br \/>\n}<br \/>\nconstructor() {<br \/>\nthis._appConfig = this.buildConfig();<br \/>\n}<br \/>\nget(): AppConfig {<br \/>\nreturn this._appConfig;<br \/>\n}<br \/>\n}<\/p><\/blockquote>\n<p><strong><u>app.component.ts \u2013 example configuration consumer<\/u><\/strong><\/p>\n<blockquote>\n<pre>@Component({\r\n  selector: 'app-root',\r\n  templateUrl: '.\/app.component.html',\r\n  styleUrls: ['.\/app.component.scss']\r\n})\r\nexport class AppComponent implements OnInit {<\/pre>\n<p>constructor(<br \/>\nprivate appConfigService: AppConfigService<br \/>\n) {}<\/p>\n<p>public get appConfig(): AppConfig {return this.appConfigService.get(); }<\/p>\n<p>ngOnInit(): void {<br \/>\ndocument.title = this.appConfig.appTitle;<br \/>\n}<\/p><\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>Key Goals The primary goal is to completely decouple configuration data from the build deployment. This is a key requirement in an enterprise setting, not least because it is vital that the very same build can be tested and signed off in a number of different development and test environments pointing at different server infrastructure.\u00a0 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[198,200,207],"tags":[199,201,16,15,208],"_links":{"self":[{"href":"https:\/\/salientsoft.co.uk\/index.php?rest_route=\/wp\/v2\/posts\/1998"}],"collection":[{"href":"https:\/\/salientsoft.co.uk\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/salientsoft.co.uk\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/salientsoft.co.uk\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/salientsoft.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1998"}],"version-history":[{"count":33,"href":"https:\/\/salientsoft.co.uk\/index.php?rest_route=\/wp\/v2\/posts\/1998\/revisions"}],"predecessor-version":[{"id":2152,"href":"https:\/\/salientsoft.co.uk\/index.php?rest_route=\/wp\/v2\/posts\/1998\/revisions\/2152"}],"wp:attachment":[{"href":"https:\/\/salientsoft.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1998"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/salientsoft.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1998"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/salientsoft.co.uk\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1998"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}