Archive for February, 2020

February 20th, 2020
4:34 pm
Loading/Busy Indicator

Posted under Angular & PrimeNG & Web
Tags

I needed an indicator such as some kind of spinner whilst microapps were loading.

PrimeNG has a progress spinner here. However, the implementation uses scalable vector graphics, which I was not able to use.

My base line for browser support included IE11, and unfortunately this only has a partial implementation which broke when using the PrimeNG spinner.

I therefore used loading.io to design my own animated gif. The site has a number of free as well as paid for spinner/busy patterns, and each one has a form allowing a number of variables to be tweaked, allowing customisation of your spinner design. You can then download the result in a choice of formats including animated GIF as well as SVG. I stuck with an animated GIF as this worked across all the browsers. I designed different versions (e.g. colour), making them themeable as part of the application theme. For each base theme I implemented a choice of 2 spinners – A rolling spinner, and a spinning circles spinner. Both gave excellent results and were free.

 

Comments Off on Loading/Busy Indicator

February 20th, 2020
4:19 pm
Deep merging of JS objects

Posted under Angular & Web

I needed to do this to implement a configuration profile/configuration override pattern, whereby a configuration profile contained json configuration which would selectively override some configuration options by merging the profile into the overall configuration.

This post here discusses deep merging of objects, and gives examples such as this one, which I used as the basis of my implementation. I did not need or want to get into deep merging of arrays, which other answers discussed, and I did not want to involve lodash.

 

Comments Off on Deep merging of JS objects

February 20th, 2020
3:15 pm
Using the Angular APP_INITIALIZER

Posted under Angular & Web

This allows the use of asynchronous operations (such as configuration loading) at Angular bootstrap time. It is documented (somewhat minimally to say the least) here.

In my case I needed it to allow early script loading of (in my case Yoti) external scripts at bootstrap time. A Yoti script overwrote some promise code that had already been patched by zone.js earlier. Subsequently, zone.js detected this and the application failed. This is documented here. I also used it to load the app configuration dynamically, rather than from index.html. As well as being a better design due to decoupling from index.html and opening the door to pulling the configuration from an external provider, this also allowed multiple configuration sets to be selectable e.g. from a query string parameter in the url.

This Stack Overflow post shows how to implement the APP_INITIALIZER.  This tutorial post does the same, and goes into details about using multiple providers via a multi provider token. Basically you need to pass a function or a lambda which returns a promise, which completes when your asynchronous operation is done. One point worthy of note is that when rejecting a promise, you need to pass something in the reject, or Angular throws an error stating Cannot read property ‘ngOriginalError’ of undefined.

My implementation is here:

@NgModule({
declarations: [
AppComponent,
FabricComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,

],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
…,
ConfigurationService,
ConfigurationValidator,
ScriptLoader,
PreBootScriptLoader,
ObjectUtils,
{
provide: APP_INITIALIZER,
useFactory: (configurationService: ConfigurationService, preBootScriptLoader: PreBootScriptLoader) => () => {
  return configurationService.load().then(() => preBootScriptLoader.load);
},
deps: [ConfigurationService, ConfigurationValidator, PreBootScriptLoader, ScriptLoader, ObjectUtils],
multi: true
},
],
bootstrap: [AppComponent]
})

Promise rejection code fragment showing passing an error in the reject:

if (this.configurationValidator.validate(appConfig)) {
  console.error(‘Configuration failed validation’, appConfig);
  reject(new Error(‘Configuration failed validation as above.’));
} else {
  resolve();
}

Comments Off on Using the Angular APP_INITIALIZER

February 6th, 2020
10:27 pm
Zone.js gives errors if Promise code is patched via external js after zone is loaded

Posted under Angular & Web

When integrating a microapp which uses the Yoti identity platform for age verification, I hit issues where other microapps would fail to load after the Yoti/age verification microapp had loaded.

The following error appeared on the browser console:-

Error: Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.
Most likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)
at Function.push../node_modules/zone.js/dist/zone.js.Zone.assertZonePatched (polyfills.js:6911)
at new NgZone (vendor.js:42512)
at getNgZone (vendor.js:43220)
at PlatformRef.push../node_modules/@angular/core/fesm5/core.js.PlatformRef.bootstrapModuleFactory (vendor.js:43116)
at vendor.js:43162
at www.yoti.com/:1

A similar instance of this issue, where Bing loads after zone.js and similarly patches promise code, is detailed here.

In my case the culprit was the Yoti client SDK, which did the patching/overwriting. A temporary fix, which worked successfully, was to load the Yoti SDK in index.html, prior to the angular bootstrap process, and remove/disable my existing code which loaded the SDK dynamically via a script tag (after zone had loaded).

My final solution was to use an Angular app initializer token, which permits a custom initialise function to be created. This can perform  an asynchronous operation to be performed which returns a promise to the app initialiser, which allows bootstrapping to continue only when the promise has succeeded.

The details of this implementation are in a separate post, here.

Comments Off on Zone.js gives errors if Promise code is patched via external js after zone is loaded

February 6th, 2020
7:43 pm
Remote loading of Angular Elements from separate webpack bundle causes unwanted multiple bootstraps

Posted under Angular & Web

I have used this pattern here from Manfred Steyer (a Google Developer Expert who has been involved in coding the Angular 8 Ivy release), to allow remote loading of separately developed and packaged Angular Elements, each of which is build like a separate app.

Whilst the application was able to successfully load and display a number of angular elements as separate microapps, the bootstrapping process had issues, manifested with console errors stating ‘zone.js already loaded’.

This post here discusses this kind of issue. Whilst I did not experience the same failure as detailed in the post, it was a related issue.

It has also been raised as an angular issue here.

Rob Wormald’s comment of 14/5/18, after he looked into it, indicated that renaming the global webpackJsonp objects to something unique in each bundle would solve the problem, but was not a recommended solution. From the discussion in the post comments, it looks like Angular 8 and 9 have a better solution to this issue.

I am not yet able to upgrade Angular from the 6.1.7 I am currently using to 8 or 9. PrimeNG has compatibility with the latest Angular version, and also switching to a much later PrimeNG would cause me theming issues as Prime have dropped the legacy theming for a newer, paid for, non open source theme creator, which raises further issues.

As my current work is a POC, I therefore took a pragmatic approach and implemented Rob Wormald’s idea as above. One of the posters in the thread above took this approach using a custom webpack solution as detailed here. I took a similar approach but avoided the custom webpack solution by using a simple NPM post build script, written in javascript, run with Node, which runs after the webpack bundle has been created. The script uses the replace plugin to rename the objects in the files. One reason was that I wanted to be decoupled from any direct webpack customisation via a webpack build plugin as from the comments there can be issues getting this to work depending on the actual Angular version in use. I have seen comments that the angular development team are keen to encapsulate/hide the internals of webpack building to allow them the complete ability to control and change it. In fact this has happened with later angular versions.

Whilst any such solution will be temporary and will need to be swapped out when eventually moving to Angular 9 or later, this solution completely solved the problem, and zone.js was no longer loaded multiple times.

The solution involved the following steps:-

1) Add a postbuild step in package.json, to run the post build script:-

“scripts”: {
“ng”: “ng”,
“start”: “ng serve”,
“prebuild”: “cd src/assets/resources/themes/salient && node-sass theme.scss theme.css –source-map .”,
“build”: “ng build”,
“postbuild”: “node post-build.js”,

The postbuild step is automatically run when you use “npm run build” to do a build. This step edits the built bundle to do the required renaming.

Note also that in the above example I also have a prebuild step, which is used to build SCSS themes which are deployed as static assets outside the bundle, to allow dynamic theme selection. The prebuild step also runs automatically when “npm run build” is done.

2) The post build script uses the npm replace package. Note that using the replace from javascript rather than a CLI command avoids the collision with the standard Windows replace, and also allows a tidier way of handling the various components of the Webpack script bundle.  An example of the code follows. Note that as node uses the V8 javascript engine, array maps and lambdas “=>” are available to use:-

var replace = require(“replace”);

/* arguments: Object suffix, project folder name (dist subfolder),
*            list of all the generated webpack scripts to be updated */
doWebpackReplace(‘MicroappFabric’,’microapp-fabric-prototype’,
[‘main.js’, ‘polyfills.js’, ‘runtime.js’, ‘styles.js’, ‘vendor.js’]);

function doWebpackReplace(name, filename, paths) {
   replace({
      regex: ‘\\[“webpackJsonp”\\]’,
      replacement: `[“webpackJsonp${name}”]`,
      paths: paths.map((p) => `dist\\${filename}\\${p}`),
      recursive: false,
      silent: false,
   });
}

3) Make sure the npm file replace package is installed via “npm install –g replace”, and make sure it is listed in the “dependencies” section of package.json:-

“primeng”: “6.1.3”,
“replace”: “^1.1.5”,
“rxjs”: “6.3.2”,

Comments Off on Remote loading of Angular Elements from separate webpack bundle causes unwanted multiple bootstraps