Blog Archives

February 21st, 2022
5:59 pm
Configuring Wake On Lan with Windows 10/11 and Fritzbox 7530

Posted under Hardware & PC & Windows & Windows 10 & Windows 11
Tags ,

As I found previously, there were a number of things to get right to make this work, as follows:-

1/ The bios settings for the motherboard need to be set correctly to enable it, and whether/how to do this varies depending on the motherboard. My older Gigabyte Z77-D3H at version F18 did not have a setting to enable this, but it turned out that it worked anyway once all the other issues were correctly address as below. My newer Asus Prime Z690M-Plus D4 did need a setting changed, and this was somewhat subtle. For this motherboard, from the home page you need to visit Advanced Mode/Advanced/APM Configuration/Power on by PCI-E, and enable this option. Note that you only find out from the prompt when you actually visit this option, which is labelled as PCI-E, that it also affects the onboard LAN as well as any PCI-E adapter. This was not clear.

2/ In Windows 10 and 11, open the control panel and visit Hardware and Sound/Power Options, and then select “Choose what power buttons do” on the left menu. Then click “Change settings that are currently unavailable” at the top, and this enables the “Turn on fast startup” option, which should be disabled. Whilst I am not certain that this is required, it was cited in this post re wake on lan, and turning it off did not slow boot time noticeably on my PCs, so I left it on.

3/ You then need to change the network adapter settings. Open the device manager and located your network adapter. Check the advanced settings and ensure that Wake on magic packet is enabled. Then, under the power management tab, allow this device to wake the computer, ensure that Only allow a magic packet to wake the computer is enabled. Again, I am not certain that the latter is absolutely required and I did get some intermittent behaviour when testing wake on lan, but in the end I have left this enabled, as I have no current requirement for any other way to wake the computer.

4/ On the Fritz Box 7530, wake on lan is built in. Navigate to Home Network/Network using the menus on the left, and then select the device that you want to wake. Note that it may be under active connections or idle connections in the list, and it is not clear what an idle connection means – a pc that is on/booted can appear in the idle connections list. Either way, this does not matter. You just click on the pencil icon as if to edit the settings for that device (even though you are not changing anything, this is where you will find wake on lan). Scroll to the bottom and you will see a button labelled Start Computer, which will successfully perform a wake on lan if all is in order. Note that to the left of this button is a check box labelled Start this computer automatically as soon as it is accessed from the internet. Whilst it might be convenient to enable this to save a manual wake on lan via the fritz box when accessing from the internet, I have elected not to do so at present as my needs for this are infrequent and it gives additional protection for the lan, as a remote fritz box access is needed to trigger this, which is of course password protected. However, doing it automatically would be a lot more convenient as accessing the fritz box remotely to do the wake on lan is perfectly possible and relatively straightforward, it does require several steps.

Once all this was done, I achieved consistent wake-on-lan behaviour using my Fritz Box 7530 to perform the wake on lan, as per this post here.

Comments Off on Configuring Wake On Lan with Windows 10/11 and Fritzbox 7530

January 25th, 2022
5:19 pm
Dismounting a Paragon Backup Image

Posted under Windows & Windows 10
Tags , ,

I experimented with mounting a backup image from a particular backup point on a job, using the Backup software.
This worked fine, and a new drive letter was created which could be browsed directly via windows explorer.
I was not clear how to dismount the image, and the documentation said that it would only remain mounted until a reboot anyway.

In fact, after rebooting, the image remained mounted in error even if the backup disk was not available (obviously it could not be fully browsed). I managed to remove it consistently by de-assigning its drive letter in disk management, then rendering it offline in disk management, and then using device manager to delete the UIM device for the mounted image, which appeared under disk devices. This worked ok providing all the steps were done in the right order, otherwise it appeared to remain around after a reboot.

After trying again in the backup software, I found it was a simple user error – the mount screen allows selection of a drive letter, and if one is already selected you can select “None” again to remove it and dismount the image. This was not immediately obvious the first time around – I failed to notice that “None” was available in the list as it was hidden and you need to explicitly scroll to the very top of the list to see it.

Once I dismounted the image this way, it worked immediately and completely removed the image and the device even without a reboot.

Comments Off on Dismounting a Paragon Backup Image

February 10th, 2021
6:54 pm
Pinning a title bar on a page with Angular 11/PrimeNG 11

Posted under Angular & PrimeNG & Uncategorized & Web
Tags ,

My initial attempt on this was to use static positioning when the title bar was unpinned (e.g. scrollable), and fixed positioning when the bar was pinned.

When using fixed positioning as above, the title bar is no longer in the normal flow so the rest of the content (in my case, encapsulated in a main container div) will sit underneath the title bar. I therefore used a top margin when pinned to prevent the top of the content being hidden. Some issues arose with this as follows:

1/ It was necessary therefore to add an additional margin to the main container to avoid occluding the content under the title bar, which meant exactly knowing its height. In addition, to allow a gap between the title bar and the content when pinned, I added a border to the title bar of the same colour as the page background, in effect to act as a kind of margin. This worked fine.

1/ My title bar was floated, so it automatically resized to fit its content. this meant that the margin allowance on the main content div would have to vary dynamically on resize.

2/ One way around this was to set media query breakpoints to trigger on the resize, and to set a fixed height for the title bar rather than floating it, and setting the margin on the content to match in each media query. In addition to this, I implemented a flexbox solution where the container was sized and the content centered within it. I was not so keen on this as it lost the ability to dynamically resize the title bar container as needed by the content resizing, and might cause problems if the layout/text changed in the title bar for example.

3/ Another way was to use a resize event and change the size of the margin on the content programmatically, using an rxjs debounced resize event. This worked pretty well, but I needed to switch the content margin on and off when the title bar was pinned/unpinned. This should have worked ok, but I noticed some slight visual movements in some page elements on pinning change, even though I could not see any style or layout changes in the browser inspector. This was annoying.

4/ I then tried using absolute positioning of the title bar when unpinned, and fixed positioning when pinned. This relied on using the same programmatic margin on the content for both, and in fact simplified the code, as I did not have to switch off the programmatic margin change when unpinning the title bar – it was used all the time, as absolute positioning was also outside the normal flow. Also, the slight glitches I saw on the page were completely eliminated.

I therefore stuck with method 4/ as the final solution. Some sample code showing the programmatic resize is as follows:-

private readonly resizeDebounceTime = 100;
private resizeObservable!: Observable<Event>;
private resizeSubscription!: Subscription;
private readonly formatPixelSize = (pixels: number) => `${pixels}px`;

ngAfterViewInit(): void {
   this.initBannerSpacingAdjustment();
}

ngOnDestroy(): void {
   this.destroyBannerSpacingAdjustment();
}

private initBannerSpacingAdjustment(): void {
   this.adjustBannerSpacing();
   this.resizeObservable = fromEvent(window, 
      ‘resize’).pipe(debounceTime(this.resizeDebounceTime));
   this.resizeSubscription = this.resizeObservable.subscribe(event => 
      this.adjustBannerSpacing());
}

private destroyBannerSpacingAdjustment(): void {
   if (this.resizeSubscription) {
      this.resizeSubscription.unsubscribe();
}
}

private adjustBannerSpacing(): void {
   /* note that the offsetHeight is the total height of the banner,
    * including padding and border */
   this.mainContainer.nativeElement.style.marginTop =
      this.formatPixelSize(this.banner.nativeElement.offsetHeight);
}

 

Comments Off on Pinning a title bar on a page with Angular 11/PrimeNG 11

February 8th, 2021
6:04 pm
Using Font Awesome with Angular 11/PrimeNG 11

Posted under Angular & PrimeNG & Uncategorized & Web
Tags ,

Update 4/3/21

I had problems again when trying to display font-awesome icons in a microapp/web component. In this case, my parent fabric application which loaded the web component had font awesome loaded in package.json but not in angular.json. I removed font-awesome from the web component application in both package.json and angular.json as the parent had loaded it in both places. This then worked correctly. There may be a similar issue with e.g. the prime icons etc. but have not researched this. Clearly PrimeNG itself is needed in both applications as the child web component has development dependencies on it. My theme styling however is only loaded in the parent fabric, and it makes sense that font-awesome behaves the same way.

Original Post

My previous post about using font awesome with Angular  up to v6 and PrimeNG up to v6 is here

With PrimeNG 11, Prime have introduced their own icon set, PrimeIcons.

I wanted to use some font awesome icons as well as they have a larger set, but initially they would not display. When trying again later it all worked fine, and am unsure what I got wrong the first time around. To be clear, the following steps were needed:

1/ Install font awesome and add to package.json (–save adds to package.json)

npm install font-awesome –save

2/ Add a style reference to angular.json

“styles”: [
“node_modules/primeng/resources/primeng.min.css”,
“node_modules/font-awesome/css/font-awesome.min.css”,
“node_modules/primeicons/primeicons.css”,
“src/messages.scss”,
“src/styles.scss”
],

Double check that the referenced css file actually exists under node-modules for the version in use. in my case, this was 4.7.0.

I was looking for pin/unpin icons for a banner bar, and could only find a vertical pin, fa-thumb-tack – I also wanted a horizontal pin to designate the unpinned state. Fortunately, font awesome has the ability to transform icons e.g. to rotate them. This is more limited in v4, but rotation is allowed, so my reference was as follows:

<p-toggleButton [(ngModel)]="bannerPinned" onIcon="fa fa-thumb-tack" offIcon="fa fa-thumb-tack fa-rotate-90"
class="ss-pin-banner"></p-toggleButton>

This worked perfectly. v5, which was not available directly to install via npm at the time, allows more transformations. The transforms are detailed in this stack overflow post and this font awesome page.

 

 

Comments Off on Using Font Awesome with Angular 11/PrimeNG 11

January 27th, 2021
6:34 pm
Creating and compiling a Custom PrimeNG 11 theme deployed as a static asset

Posted under Angular & PrimeNG & Uncategorized & Web
Tags ,

This follows on from this earlier post for Angular 6.

PrimeNG has for some time removed support for legacy themeroller themes and added their own architecture and theme editor. However, for the first time, the theme editor and the SCSS theming interface/standard SCSS files are no longer open source, but are a paid for option. The rest of the component set, including the delivered compiled css for all the standard themes, is available as open source as before.

Therefore, having considered the options, I elected to take a standard theme that was close to what I needed and edit its css directly. This is suggested as an option by Prime for small changes, but it is possible that later changes to the theme architecture could require theme changes as we are editing the compiled css. I decided that the risk and rework that might be needed was small, so have taken this route.

My policy on this is to copy the theme files to a new theme folder under assets/resources/themes/, and then to rename the theme.css file to theme.scss, as the compiled css is also valid scss. This then allows me to add my own variables as required for theme colours etc. to avoid duplication. The intention is to minimise alterations to the original theme, so I would only add variables where I had made changes, to minimise any rework if I had to take a new version of the standard theme and add my changes again.

Whilst doing this, I noted a warning that node-sass, as used previously, is now deprecated. I therefore switched to sass, as per this stackoverflow post, using npm uninstall -g node-sass followed by npm install -g sass. This worked fine, and my changes to the existing compilation script in package.json (also allowing multiple themes to be compiled) were as follows:-

 “scripts”: {
“ng”: “ng”,
“start”: “ng serve”,
“build”: “ng build”,
“test”: “ng test”,
“lint”: “ng lint”,
“e2e”: “ng e2e”,
“scss”: “npm run scss1 && npm run scss2”,
“scss1”: “cd src/assets/resources/themes/salient-new && sass theme.scss:theme.css –source-map .”
“scss2”: “cd src/assets/resources/themes/salient-new2 && sass theme.scss:theme.css –source-map .”
},

 

Comments Off on Creating and compiling a Custom PrimeNG 11 theme deployed as a static asset

March 2nd, 2019
11:22 pm
Compiling a custom PrimeNG theme deployed as a static asset

Posted under Angular & PrimeNG
Tags ,

I am  using multiple themes and theme selection, using custom themes

For interest I am loading the theme css as a static asset, in index.html:-

<head>
<meta charset=”utf-8″>
<title>Microapp Fabric Prototype</title>
<base href=”/”>
<meta name=”viewport” content=”width=device-width, initial-scale=1″>
<link id=”theme-css” rel=”stylesheet” type=”text/css” href=”assets/resources/themes/salient/theme.css”>
</head>

Then I am dynamically changing the href to switch themes:-

export class ThemeSwitcher {
/*
* Select the target theme
* this may be an external theme or an internal packaged one
* If the path contains a ‘/’ the theme is assumed to be an external theme source
*/
changeTheme(theme: string) {
const themeLink: HTMLLinkElement = <HTMLLinkElement>document.getElementById(‘theme-css’);
themeLink.href = theme.indexOf(‘/’) >= 0 ? theme : ‘assets/resources/themes/’ + theme + ‘/theme.css’;
}
}

A theme SCSS under the assets folder will be automatically compiled and included inline in the package only when a specific theme is referred to in the “style” section of angular.json. When dynamically switching multiple themes as above, the themes are kept as static assets and are not inlined in the package and so are removed from the “style” section. When this is done, the theme scss files for all the themes are not compiled to css automatically when an ng build or ng serve is done. This post here details how to manually install and build the scss using node-sass. It goes into other details which I haven’t needed as I am using static assets, so only node-sass is needed. If in the actual theme directory, the following commands will install  node-sass and compile the scss for the theme and also create the source map:-

npm install node-sass

node-sass theme.scss theme.css ––source-map .

This can also be added to the scripts section of package.json to make an npm command:

“scripts”: {
“ng”: “ng”,
“start”: “ng serve”,
“build”: “ng build”,
“test”: “ng test”,
“lint”: “ng lint”,
“e2e”: “ng e2e”,
“scss”: “cd src/assets/resources/themes/legacy-blue && node-sass theme.scss theme.css ––source-map .”
},

This can be run by simply using npm run scss

Note as above that npm supports the cd command in the scripts section, both for linux and windows (as detailed here). The default directory is the root of the project, and this is consistent even if you run the npm command e.g. from a subdirectory. I did not manage to find a way to automate this via an ng build or ng serve command, so these would have to be run in conjunction with npm run scss (with the latter running first).

No Comments »

May 23rd, 2018
7:51 pm
Installing and running the PrimeNG showcase locally under windows

Posted under Angular & PrimeNG
Tags , , ,

Update 23/5/2018

I retried this using Angular 6 and the latest primeng.

Step 2/ below was not needed, and I did not install/reinstall gulp per step 3/ (not used this time – need to check/confirm re this)

The wiki steps referred to below, here, referred to using sass to build the css, but this command did not work for me even when I installed the node.js sass per here -  the –update command came back with an error.

I then gave up using sass to compile the sass files.

Because of this issue I still had to fix the theming, so tried to copy the css files as below rather than compile the sass. This time the themes folder was under src\assets\components.

I therefore created another skeleton app, installed primeng using “npm install primeng”, and then copied the themes folder from under node_modules\primeng\resources  to src\assets\components, overwriting the themes already there.

This added the css files that were missing.

I could then run the showcase with theming fully working, either using “npm start” or “ng serve” – either worked.

I did notice that the theming borders on the treetable component did not appear to work, but apart from that all the theming including theme switching worked fine.

Original Post

It is useful to have this running locally in order to can try out occasional code hacks on the example code to try out ideas or check how things work if the docs are not clear on a point, knowing that you have started from a working example.

The steps for this basically follow the wiki steps here. Note the following additions/amendments to the steps:-

1/ I cloned the repository from here. Having cloned I then listed and disconnected from the remote repositories, as the clones were just for local use:-

git remote –v

git remote rm origin

git remote –v

2/ Dependencies as follows were added to package.json as per instructions:-

  "dependencies": {
      "@angular/common": "4.0.0",
      "@angular/compiler": "4.0.0",
      "@angular/core": "4.0.0",
      "@angular/forms": "4.0.0",
      "@angular/http": "4.0.0",
      "@angular/platform-browser": "4.0.0",
      "@angular/platform-browser-dynamic": "4.0.0",
      "@angular/router": "4.0.0",
      "@angular/animations": "4.0.0",
      "core-js": "^2.4.1",
      "rxjs": "^5.2.0",
      "zone.js": "^0.8.4"
  },

Following this npm install was run to download all the dependencies

3/ In order to build using gulp as instructed (gulp build), I installed gulp first as per the instructions here:-

npm install --global gulp-cli
npm install --save-dev gulp

4/ I then ran npm start to run the showcase, which defaults to http://localhost:8080/

The showcase ran ok locally but there were some issues with the theming – the theme had a transluscent background on the menus and dropdowns for example which did not correctly occlude the text underneath. The live showcase on the PrimeNG site did not have this problem.

On investigation it appears that the theme files are under the main resources/themes directory, but are not being sass compiled from scss during the build so there are no .css files there. The gulp file for the project, gulpfile.js, does not have any entries that do sass compilation so this may be an omission/bug in the version I had downloaded.

The showcase does not use the primeng under node_modules – it defines its own primeng package such that if you try to enter npm install primeng to install the themes in the normal way as part of the npm installed package, you get an error from npm about the duplicate primeng.

My workaround was straightforward – I just copied the theme folder and all subfolders from under the node_modules\primeng\resources directory in another application folder to the projects top level resources directory (in fact I used the primeng-quickstart-webpack which I had already set up) – any other project where you have installed primeng will do. This solved the problem by providing all the css files for the themes, and the themes and theme switching then worked.

As I have no requirement to do a full working build for the showcase this is no problem.

I checked for other posts about theming issues and found a couple of StackOverflow posts about theme issues with Webpack here and here. These may prove relevant for future development.

No Comments »

August 6th, 2017
6:19 pm
Json Web Tokens

Posted under Web
Tags ,

Lots of pros and cons re this – not to mention flame wars online!

The basics:-

  1. A digitally signed token with a header, a payload of ‘claims’ or assertions, e.g. ‘I’m a superuser’, and a digital signature.
  2. The digital signing allows a server to confirm both that the token is ‘one I prepared earlier’, i.e. one of it’s own, but crucially also that ‘the content e.g. the claims have not been modified’
  3. In theory and on the face of it this allows a server to be stateless and avoids the need for session state/sticky sessions and the like – the session state can be passed in the JWT to the client.
  4. There are problems – one is access revocation. If the server decides ‘I am going to cancel your session now because I doubt your security’ it has no way of revoking previously created tokens. A stateless server would continue to honour an old token.
  5. A revocation list can help a server to address this – a list of JWTs that it knows are revoked. However we are now back to server side state again, albeit less of it.
  6. A server can change its digital signing key to obviate this but this would invalidate every single JWT, not just a single one.
  7. However, a valid technique could be for the server to mandate token reissue periodically, i.e. change its digital signing and require clients to poll the server to refresh their tokens. However the server would still need to perform some kind of revocation check on token reissue.
  8. If a JWT can be stolen then anyone can reuse it. Issues such as Cross Site Request Forgery can be an issue, see also here. Where you store your JWTs client side is important, e.g. local storage vs cookies.

This post argues strongly that JWTs are not suitable as a server side session substitute. A number of the points seem overly opinionated and not entirely balanced, but it does make a number of good points.

This google docs page gives a more balanced comparison chart of a number of API authentication techniques and is worth a read.

No Comments »

July 22nd, 2017
10:52 pm
AngularJS – Controller Lifecycle and using $scope lifecycle hooks

Posted under AngularJS
Tags ,

This post here answers a basic question I had as to what is the AngularJS controller lifecycle.

The post links to a plnker which demonstrates some of the lifecycle hooks – $viewContentLoaded and $destroy.

These posts relate to the events used/hooked in the example – but more digging is needed to expand on this area:

https://docs.angularjs.org/api/ngRoute/directive/ngView

https://stackoverflow.com/questions/28800426/what-is-on-in-angularjs

https://docs.angularjs.org/api/ng/type/$rootScope.Scope

Unfortunately my first attempt at trying this in an angularjs-up-and-running example failed with $scope being undefined.

I initially used this code pattern to pass $scope into the controller:

angular.module('notesApp', [])

.controller('SubCtrl', [function($scope) {

console.log('SubCtrl has been constructed');
$scope.$on('$viewContentLoaded', function() {
console.log('SubCtrl has loaded');
});
$scope.$on('$destroy', function() {
console.log('SubCtrl is no longer necessary');
})

var self = this;
self.list = [
{id: 1, label: 'Item 0'},
{id: 2, label: 'Item 1'}
];

self.add = function() {
self.list.push({
id: self.list.length + 1,
label: 'Item ' + self.list.length
});
};
}]);

The problem above is that the controller function is passed as an array (to avoid problems during minification) and so $scope has to be passed differently as per this post here.

angular.module('notesApp', [])
.controller('SubCtrl', ['$scope', function(scope) {

console.log('SubCtrl has been constructed');
scope.$on('$viewContentLoaded', function() {
console.log('SubCtrl has loaded');
});
scope.$on('$destroy', function() {
console.log('SubCtrl is no longer necessary');
})

var self = this;
self.list = [
{id: 1, label: 'Item 0'},
{id: 2, label: 'Item 1'}
];

self.add = function() {
self.list.push({
id: self.list.length + 1,
label: 'Item ' + self.list.length
});
};
}]);

The complete working example follows:

app.js

// File: chapter5/need-for-service/app.js
angular.module('notesApp', [])
.controller('MainCtrl', [function() {
var self = this;
self.tab = 'first';
self.open = function(tab) {
self.tab = tab;
};
}])
.controller('SubCtrl', ['$scope', function(scope) {

console.log('SubCtrl has been constructed');
scope.$on('$viewContentLoaded', function() {
console.log('SubCtrl has loaded');
});
scope.$on('$destroy', function() {
console.log('SubCtrl is no longer necessary');
})

var self = this;
self.list = [
{id: 1, label: 'Item 0'},
{id: 2, label: 'Item 1'}
];

self.add = function() {
self.list.push({
id: self.list.length + 1,
label: 'Item ' + self.list.length
});
};
}]);

index.html

<!-- File: chapter5/need-for-service/index.html -->
<html ng-app="notesApp">

<head>
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script src="app.js"></script>
</head>

<body ng-controller="MainCtrl as mainCtrl">
<h1>Hello Controllers!</h1>
<button ng-click="mainCtrl.open('first')">Open First</button>
<button ng-click="mainCtrl.open('second')">Open Second</button>
<div ng-switch on="mainCtrl.tab">

<div ng-switch-when="first">
<div ng-controller="SubCtrl as ctrl">
<h3>First tab</h3>
<ul>
<li ng-repeat="item in ctrl.list">
<span ng-bind="item.label"></span>
</li>
</ul>

<button ng-click="ctrl.add()">Add More Items</button>
</div>

</div>
<div ng-switch-when="second">
<div ng-controller="SubCtrl as ctrl">
<h3>Second tab</h3>
<ul>
<li ng-repeat="item in ctrl.list">
<span ng-bind="item.label"></span>
</li>
</ul>

<button ng-click="ctrl.add()">Add More Items</button>
</div>
</div>
</div>
</body>

</html>

No Comments »

June 6th, 2017
5:58 pm
Angular/Ionic–externalising application configuration

Posted under Angular & Ionic & TypeScript
Tags , , , ,

Key Goals

  1. 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.  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 The Twelve Factor App site here.
  2. 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 – 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.
  3. I am using angular-cli with webpack, which is the recommended approach. This produces simple static deployment bundles which are web server agnostic – 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 – any such mechanisms (discussed further below) would be an extension to the pattern for a particular server.
  4. 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.
  5. 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.

 

Solution Overview

  1. After a fair amount of investigation I settled on a fairly simple pattern as per this post here. This stackoverflow comment refers to the same technique.  I adapted the technique for my situation – the key points are as follows, and a code sample is shown below.
  2. The basic idea is to load configuration data as a Json object literal from a .js file. This is done by a <script> 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 src of the <script> tag is deemed not part of the build.
  3. 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 src 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.
  4. 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 <script> 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.
  5. When running in development mode using files (e.g. via lite-server using  ng build –watch and running lite-server from the dist subdirectory), a config file can be used in the normal way to override the default config if desired.
  6. 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.
  7. 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.
  8. 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 ERROR in Error encountered resolving symbol values statically. In the end I went with an injected configuration service, which consuming classes set up as a getter to allow convenient access.
  9. 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.

 

Example Code Listings

 

index.html

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Places Admin</title>

<base href=”/”>

<meta name=”viewport” content=”width=device-width, initial-scale=1″>
<link rel=”icon” type=”image/x-icon” href=”favicon.ico”>

<script type=”text/javascript” src=”appConfig.js”></script>

</head>
<body>
<app-root>Loading…</app-root>
</body>
</html>

appConfig.js – external configuration file.

// Application Configuration
var salientSoft_appConfig = {
  appTitle: "The Places Guide",
  apiBase: "http://localhost:5984/places-guide/",
  messageTimeout: number = 3000
};

environment.ts

// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.

import {AppConfig} from ‘../config/app-config’;

export const environment = {
production: false,
};

export const defaultAppConfig: AppConfig = {
appTitle: ‘The Places Guide’,
apiBase: ‘http://localhost:5984/places-guide/’,
messageTimeout: 3000
};

environment.prod.ts

import {AppConfig} from '../config/app-config';

export const environment = {
production: true
};

export const defaultAppConfig: AppConfig = null;

appConfig.ts – config file interface

/* Root application configuration object
   This encapsulates the global config json properties.
   These must match what is in the actual config file, normally /appConfig.js.
 */
export interface AppConfig {
    readonly appTitle: string;
    readonly apiBase: string;
    readonly messageTimeout: number;
}

app-config.service.ts

import {Injectable} from '@angular/core';
import {AppConfig} from './app-config';
import {defaultAppConfig} from '../environments/environment';

@Injectable()
export class AppConfigService {
private _appConfig: AppConfig;
buildConfig(): AppConfig {
const appConfig: AppConfig = (<any>window).salientSoft_appConfig || defaultAppConfig;
return Object.freeze(appConfig);
}
constructor() {
this._appConfig = this.buildConfig();
}
get(): AppConfig {
return this._appConfig;
}
}

app.component.ts – example configuration consumer

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {

constructor(
private appConfigService: AppConfigService
) {}

public get appConfig(): AppConfig {return this.appConfigService.get(); }

ngOnInit(): void {
document.title = this.appConfig.appTitle;
}

No Comments »