Archive for the 'Angular' Category

February 6th, 2024
1:52 pm
Progressive Web Apps – Dynamic Cache Script Creation

Posted under Angular & Knowledge Base & PWAs & Web
Tags ,

My PWAs dynamically create and load a url cache in the service worker, when the installation event fires.
As this cache contains all the files in the dist/project-name directory tree (i.e. the entire deployment) for an angular app, I was keen to create the script containing the list of files to be cached dynamically as a post build step, so that the post build script could be generic and adaptive to the build contents.

This turned out to be pretty straightforward. The following points are noteworthy:

  1. I wanted to dynamically create a JS file which defined an array containing the urls of all the files to be cached. This script would then be included by the service worker to be used when it created and loaded the cache, during its install event.
  2. The above JS file was imported by the service worker script. I had some issues getting this to work via modules/module support, and noted from this post here that using the importScripts function instead (e.g. importScripts(‘pwa-cache-urls.js’);) gave better cross browser support/backwards compatibility. I therefore switched to using importScripts and it all worked fine.
  3. glob.sync was used to generate an array containing all the deployed files to be cached, excluding directories using {nodir: true}. This was then mapped/formatted as required into a string containing all the script for the file.
  4. fs.writeFileSync was used to create the script file from the above string.
  5. The project name and the generated script file name were passed as arguments to the post build file, in package.json. Whilst I did look for a dynamic way to derive/lookup the project name, an easy way to do this was not obvious and there was no loss of flexibility in just passing it in the postbuild script definition in package.json, along with the name of the dynamically created script file.

This all worked fine, and I was able to create a generic post build script that would be able to discover the files to be cached dynamically and create the script for them, for any angular project I was working with.

The prototype example code for the post build is below:

const glob= require(‘glob’);
const fs = require(‘fs’);

const args = process.argv;
validateArgs(args);
const projectName = args[2];
const cacheJSFileName = args[3];

const pathPrefix = ‘dist/’ + projectName + “/”;
const cacheJSFilePath = pathPrefix + cacheJSFileName;

fs.writeFileSync(cacheJSFilePath, buildCacheJSFile());

function validateArgs(args) {
  const argErrors = [
    “Project Name and Cache Filename were not passed to post-build.js”,
    “Cache Filename was not passed to post-build.js”
  ]
  if (args.length < 4) {
  throw new Error(argErrors[args.length – 2]);
  }
}

function buildCacheJSFile() {
  const mapPathDelimiters = (path) => path.replaceAll(“\\”, “/”);
  const mapPathPrefix = (path) => path.replace(pathPrefix, “”);

  const cacheJSFilePrefix = “const CACHED_URLS = [\r\n ‘./'”;
  const cacheJSFileSuffix = “\r\n]\r\n”;
  const formatCacheJSFileEntry = (file) => “,\r\n ‘” + file + “‘”;

  const cacheFiles = glob.sync(pathPrefix + “**”, {nodir: true});
  let mappedCacheFiles = cacheFiles.map((file) => mapPathPrefix(mapPathDelimiters(file)));
  let cacheJSFile = cacheJSFilePrefix;
  mappedCacheFiles.forEach( file => cacheJSFile += formatCacheJSFileEntry(file));
  cacheJSFile += cacheJSFileSuffix;
  return cacheJSFile;
}

Comments Off on Progressive Web Apps – Dynamic Cache Script Creation

February 2nd, 2024
11:14 am
Progressive Web Apps

Posted under Angular & Knowledge Base & PWAs & Web
Tags , , ,

I started looking into PWAs as an alternative to the Ionic framework which I have used previously to prototype an offline/online mobile application for my mobile places guide.

My main issue with frameworks such as Ionic was the need for multiple application codebases – one admin application for web based use online (which could also be used on mobile when online), and another application for offline or online use on mobile when actually out in the sticks and looking for a local place that best meets my already specified favourite criteria.

The immediate attraction of a PWA was the simplification due to a single codebase framework. As it is browser based, it can run with any web framework/app architecture and be usable online, and installable for offline use. Some of the main benefits are as follows:

  • Allows a single application framework such as Angular or React to be used for a web application which can be used online or installed for offline/online use just like a native app, i.e. can be run as an app from the home screen on a mobile. This allows a single application framework/codebase to be used for all scenarios. This was the key advantage for me – the need for multiple codebases/frameworks when developing an app as a small enterprise is a serious limitation. There is also the potential need for multiple offline applications/frameworks to support all the required platforms, so there can even be a need for maintaining multiple offline native app codebases.
  • PWAs are widely supported across browsers/platforms and are a standard.
  • A PWA greatly simplifies the installation process. One major issue with web sites which have a mobile app is the need to visit an app store and go through the install process before being able to continue. This really breaks the flow of use.
  • Web sites are really keen for users to install their app, as this offers functionality such as offline working and push notifications even when the app is not active/not in use. However, they typically resort to an pattern known as a full page interstitial ad. This interrupts the user, often with a full page overlay, right when they are interacting with content on the web site, in an attempt to force them to install the app – commonly called the door slam antipattern. This takes a number of steps, followed by actually running the app and returning to their previous context. A survey by Google showed that a significant percentage of users just dump the app installation and their web site activity completely when presented with the door slam.
  • PWAs have a much more seamless install process which is performed with a click directly within the browser. It does not involve visiting an app store, installing an app, and selecting options/enabling app permissions. Therefore significantly less users are put off by this.

 

Initial tryout/Proof of Concept

I branched the codebase for my existing Property Details application. This was a good example to try, as it has significant complexity:

  • Web application using the Angular framework (v13 at the current time).
  • PrimeNG component framework for angular is extensively used, including complex components such as the photo gallery
  • Dynamic scripting/script switching for configuration and PrimeNG theme switching.
  • Use of the angular APP_INITIALIZER to initialise/configure the app prior to angular bootstrapping, and use of angular/browser polyfills.
  • Use of the Webpack module bundler to bundle and install the application

I felt that if I could get this application to run seamlessly as a PWA with minimal effort and changes to the existing code, this would be a valuable proof of concept to justify taking PWAs forward.

My conclusion was very positive – whilst I had a number of technical/learning issues to overcome, I succeeded in getting the property details app to run as a PWA both online and fully offline in Chrome, edge, and Firefox, on both Windows and Android, with minimal code changes. The only code interventions were to add the necessary support for PWAs, which were minimal. None of the existing code had to be modified. I just needed to add a json PWA manifest file, a couple of scripts, and an icon image. I then just needed to modify the index.html page to declare the manifest and call the first of the scripts. Going forward, I will need to try the other PWA features such as background/offline sync, push notifications, and service worker to page/Window to service worker communication. I will also need to make some effort to smooth the app rendering to bring up a page quickly, especially when offline, as the current POC is somewhat jerky on occasion, with some delays on rendering. However, it works reliably as expected.

I also have not looked into the database functionality re offline/online use and syncing. For the database, it is tempting to stick with CouchDB, as PouchDB is available as a wrapper for IndexedDB and could be used for background sync with a CouchDB as I originally planned with my Ionic POC. One issue which needs exploring on this is the availability of CouchDB hosting – my ISP, Zen Internet, use CPanel which does not host CouchdB. If this is an issue I would need to either use IndexedDB with e.g. MySQL at the back end and my own abstraction/syncing between the two, or use continue to use PouchDB still with a MySQL back end – this whole area needs investigation.

I have therefore decided to look into using PWAs further as a replacement for my existing Ionic offline POC/tryout.

Comments Off on Progressive Web Apps

June 22nd, 2023
3:05 pm
Angular – Dynamic Routing between Angular Elements aka Web Components

Posted under Angular & Routing & Web
Tags

Following on from my previous post on dynamic routing fundamentals, the ability to route dynamically across angular elements, i.e. to use the router to route to a dynamically loaded angular element, seems to be a sticking point.

Previously, I developed a web components example where a ‘fabric’ component was responsible for dynamically creating web components and displaying them. This did not use angular routing, but dynamically added them to the dom based on menu selections or url parameters. Subsequently, I wanted to investigate whether I could use angular routing to implement this instead.

I have found a number of examples of varying degrees of complexity, which go at least some of the way. However, I have not seen what I would term a reasonably straightforward, out-of-the-box solution, which does not involve too much deep diving into the internals of angular.

The following examples have been examined to date:

A complex dynamic one here with perhaps useful ideas but actual components still appear to be declared in the angular app:
https://medium.com/ngconf/dynamic-component-generation-in-lazy-loaded-routes-d2e0f9e89c57
The code is here:
https://github.com/theAlgorithmist/Angular8LazyLoadDynamic

Some specific ideas in this one here but still some way to go IMO…
https://medium.com/@timon.grassl/how-to-use-routing-in-angular-web-components-c6a76449cdb

This one perhaps looks the most promising, and may have all the relevant techniques needed, but It still appears to have statically declared component objects which are used dynamically in a dynamic order…
https://snyk.io/advisor/npm-package/angular-elements-router

Some more research is needed on this as I am keen to fine the simplest solution which uses as little as possible of the deeper features of angular.

 

Comments Off on Angular – Dynamic Routing between Angular Elements aka Web Components

June 22nd, 2023
10:47 am
Angular Dynamic Routing

Posted under Angular & Knowledge Base & Routing & Web
Tags ,

One of my goals is to be able to dynamically create routes based on configuration data, typically loaded at pre-init time using an app initialiser.

Two approaches I have seen to doing this are as follows:

  1. Using a custom Injector to use code to inject the routes.
    This is done in this example here. The code for it is available here.
  2. Using Router.resetConfig() to change the routes after initially creating them.
    This is done in this example here. The code for it is available here.
    It is also done in this example here, in the app initialiser.

Both are dynamic, but method 1/ with the injector runs before any app initialiser, so would not be suitable for my needs (I verified this with a simple test with console logging). If this were used, any dynamic config would need to be fetched within the injector code or called directly from it. Method 2/ can be used after the app initialiser – I tried calling it successfully from the ngOnInit lifecycle hook in the app component which worked fine.

This example here uses method 1/, and the code for it is on github here. I downloaded the code and modified the original version, as it contains errors in the time of day checking code. I then saved the project to bitbucket. I branched it and modified it again to use resetConfig as above from ngOnInit, and removed the injector mechanism. This also worked fine.

Whilst these approaches allow dynamic creation of the routes, they assume that the routed components are known to the application or declared in it in some form. The basis of the standard approach to routing is that the component class referred to in the route is looked up, and its selector is extracted and used to create the component tag dynamically, inserting it after the appropriate <router-outlet>. This would not work in the case of angular elements, i.e. W3C web components. These are entirely dynamic and my examples which use these in the past have the element name/component tag defined dynamically as part of the config, along with the names of the scripts to be loaded to create the web component. Another approach will need to be investigated to look into routing of angular elements.

Comments Off on Angular Dynamic Routing

June 22nd, 2023
8:08 am
Angular Routing

Posted under Angular & Knowledge Base & Routing & Web
Tags ,

These points were found whilst trying some angular router examples.

Examples tried were these. Note that they are all on Stackblitx, and therefore zips can be downloaded.
It is not obvious how, but just make sure the project panel is open by clicking on the 2-page icon near the top left. Then click on the small cloud icon containing the down arrow, on the RHS of the project panel title.

  1. https://www.tektutorialshub.com/angular/angular-routing-navigation/
  2. https://stackblitz.com/edit/angular-ivy-spsz1g?file=src%2Fapp%2Fapp.component.ts
    Note that this one above is angular 15 so pretty up to date
Here are the official angular ones:
  1. Basic one with just 2 router states/2 buttons
    https://angular.io/guide/router-tutorial
  2. Full Tour of Heroes tutorial
    https://angular.io/guide/router-tutorial-toh 
    This above one is angular 16 so latest
 
Points on configuring and using routes:
  1. Routes are defined using RouterModule.forRoot. In some examples the route definitions/routes array is defined in app.module.ts (as in the basic angular.io example above). More typically the routes array is defined in a separate file to separate the concerns, such as app-routing.module.ts or app.routes.ts. It can then either be exported from this file and referenced in app.module.ts via RouterModule.forRoot (as in the tektutorials example above), or RouterModule.forRoot is itself also in the separate file defining the routes (as in the full Tour of Heroes example above, in app-routing.module.ts).

  2. In all the examples, routing is done by creating a component and placing it as a sibling of the <router-outlet>tag, immediately after <router-outlet>. The created component is never placed as a child of the <router-outlet>. As per the full Heroes tutorial, <router-outlets> can be nested where required – a component activated from a <router-outlet> can then use other routes to activate further child components etc.

  3. When refreshing e.g. a child page on zen apache or http-server, I got 404 errors. It is necessary to tell the server to redirect to e.g. index.html or whatever you are using, to get the routing to work. This was not the case when using “ng serve” as this takes care of the issue for you automatically. The same problem happens if you enter a url manually or from a link, to go to a detail child route page. This angular.io deployment page explains how to do this for various servers, including apache as used by Zen in my case. In my case I tested on Zen and needed to edit .htaccess as per the instructions. For this to work, I had to change the path in the instructions to the correct subdomain index.html file or it still failed. I also created a child .htaccess file in the subdomain folder where my app was hosted. This ran in addition to the main .htaccess, and it all worked fine. I did notice that the lowest child hero pages on the full tour of heroes example above still failed with a 404. However, they also failed with a 404 when served via “ng serve”, so I concluded that this was a coding issue in the example and did not investigate further. My .htaccess for the full heroes example under zen was as follows:-
RewriteEngine on

RewriteOptions inherit

# BEGIN Angular Routing

RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR] 
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d 
RewriteRule ^ - [L] 

# If the requested resource doesn't exist, use index.html 
RewriteRule ^ /angular-router/angular-full-heroes-example/index.html

# END Angular Routing


When a route is activated, its target component is always recreated and any previous component for the corresponding <router-outlet> is destroyed. I confirmed this with all the above examples by logging in the ngOnInit and ngOnDestroy life cycle methods. Components are not switched in and out by showing and hiding.

For me, if I wanted to use web components with routing, I had thought that dynamic target component recreation might create an overhead as they would need creating each time, by dynamic loading/running of all their run time js scripts (e.g. main.js, runtime.js, vendor.js). I had considered using componentless routes to avoid this overhead, and do my own creation in a route resolver.
 
However, in fact, it looks like the running of their creation scripts is a separate operation that only needs doing once. The angular router just appears to create and delete element tags from the dom, so the deletion of a tag and subsequent recreation should not require reloading the scripts, so if correct I could do the script loading in a route resolver once, the first time the script is loaded (or earlier if I am doing eager loading). I could then let angular do what it likes re adding and removing elements.
 
I intend to try this out with my existing web components/microapps demo – at present it hides/shows microapps when switching between them, but it looks like I could change this to removing/recreating the microapp elements, but only loding the script once. This would easily validate this idea.
 
In addition to this I would want to make the component name in a route definition dynamic and load it server side, and perhaps also dynamically load the route definition or at least dynamically create it from server side parameters and a template definition. This appears to be possible and this post here looks into it. This other post also looks into it with lazy loading. Significantly more investigation is needed on this.
 
 
 
 

Comments Off on Angular Routing

September 7th, 2022
4:10 pm
Running Legacy Places Guide under Windows 11 for reference

Posted under CouchDB & Hosting & Ionic & Knowledge Base & Networks & PrimeNG & Web & Windows 11
Tags ,

Having got CouchDB working under Windows 11 with the legacy places data as per this post here, I then wanted to run the legacy places angular and ionic apps if possible, just for reference and to consider my options going forward.

The angular app was in v6.1.7 which I did not want to revisit and upgrade. Similarly, the ionic app was an earlier legacy version which could be tricky to reinstate under windows 11.

However, I did have already built distributions for both apps, and both were able to run successfully under Windows 11 via http-server without having to install anything else. For angular I used the existing build under the dist subdirectory, and for Ionic I the build is under the www subdirectory. Note that whilst the index.html for the Ionic version did specify that cordova.js was required, and caused a load error in the web console, this was not an issue when using from a browser as it was not needed.

I did want to be able to run them remotely as well, from other PCs and mobiles. I tried installing the distributions under Zen hosting, with remote access to CouchDB running on the local PC. However, this would not run under HTTPS as it meant having mixed content – the access to CouchDB was not HTTPS and I did not want to go to the trouble of installing a self signed cert locally to get it all to work – this excercise was not worth the effort. I could not find an easy way under Zen cpanel of allowing just this app to be HTTP only, with everything else defaulting to HTTPS. If I turned off forced defaulting to HTTPS in cpanel, the app worked fine under HTTP but other access was also allowed to be HTTP only which I did not like, so I dumped the idea of hosting under Zen directly. I did hit a spate of nasty looking cpanel issues when I did this and for a while thought I had broken the domain/ssl/cpanel access entirely whilst messing with domain/alias/redirect settings in cpanel, but in the end it all worked fine again.

I therefore continued to run/host locally via http-server. I did want to be able to potentially remote boot my local PC via Fritz and access the app from anywhere, which would mean auto running the http-servers at boot time. The easiest way to do this turned out to be to use the windows task scheduler, which unlike services can run batch files without any other tools such as srvany which is commonly used to do this for services. Whilst the scheduler is often used with time based triggers, it is perfectly possible to specify a trigger as ‘run at boot time’. I also specified that it should run without any user logged in, therefore with local access only/no user authentication. This worked fine and I could boot the PC and then immediately access the places apps from a mobile without logging in.

Another trick needed was to add some remote port sharing in the Fritz box for both apps, via my static IP addresses. When doing this I also had to add a port share for the CouchDB access, otherwise CouchDB also complained about mixing private CORS stuff with the remote access. Once I did this, and changed the app config to use the remote URL for CouchDB access, it all worked fine. Fortunately, I could also continue to access the same apps locally on the hosting PC even though they were still using the remote URL for CouchDB access – it all worked fine.

I then set up redirects under cpanel from Zen to make the app access look a bit more friendly with salient soft urls, and this all ran fine.

 

Comments Off on Running Legacy Places Guide under Windows 11 for reference

August 5th, 2022
3:36 pm
PrimeNG V13 Picklist Component does not scroll on touch device when drag drop enabled

Posted under Angular & PrimeNG & Uncategorized
Tags , ,

In V6 or PrimeNG, you could enable drag drop reordering on the picklist and still scroll on a touch device, e.g. on a mobile phone. This was handled nicely by using a click and hold to select an item for drag drop, and a swipe to do scrolling.

In V13 this is now broken – touching on an item instantly selects it for dragging, without using the previous click and hold, so that it is not possible to scroll by swiping the source or target list any more. I have confirmed that it used to work on my old V6 microapps version which used a standard PrimeNG V6.1.3 picklist, and also have checked the current PrimeNG showcase online – the current showcase is broken in exactly the same way when trying to scroll via touch on mobile. Whilst scroll bars are present on the RHS of the list, they are very small for Mobile. I did try to enlarge the scroll bars for mobile, using some webkit specific styling, but this proved problematic as it is not fully supported for Firefox – they cannot be resized on firefox. Even when I did get it working, selecting the scroll thumbtrack for mobile proved intermittent and tricky – not a nice interaction at all.

My simple workaround for this is to disabled dragdrop for the picklist for a touch device by setting dragdrop=false (i.e. dragdrop=!touchDevice). I already have a config property in the fabric to detect a touch device, and have replicated that as a global config parameter which is visible to all the microapps. The picklist can then use this. This is actually better than it sounds – whilst you cannot reorder items within a list any more, you can move between source and target using the buttons, and drag/drop is tricky on a mobile anyway as the source and target lists appear one above the other with typically only one of them visible so dragging between them is tricky as again scrolling is involved.

For reference, changing the scrollbar styling via the webkit styles is documented here and here.

Applying the webkit styles is very picky per the posts above – spacing etc. matters – there must not be a space between the element/class name and for example ::-webkit-scrollbar or it won’t work. In the end this SCSS styling worked for me, and fortunately I was able to get it working with a classname rather than just e.g. a ul element which is the one I wanted to hit:

.p-component.p-picklist {
.p-picklist-header {
background: $ss-widget-header;
color: $ss-widget-header-text;
}

.p-picklist-list {
&::-webkit-scrollbar {
width: 50px;
height: 50px;
}
&::-webkit-scrollbar-thumb {
width: 50px;
height: 50px;
background: #a8a8a8;
border-radius: 4px;
}

As above I dumped this approach anyway, as even with a very large scrollbar and thumb track, it was difficult to consistently scroll on mobile, and anyway  the source and target for drag/drop were not both in view simultaneously anyway.

 

Comments Off on PrimeNG V13 Picklist Component does not scroll on touch device when drag drop enabled

August 4th, 2022
2:38 pm
Caching of old microapp scripts breaks new version

Posted under Angular & Uncategorized & Webpack
Tags , ,

An old chestnut this – when upgrading from an old version on my hosted environment, my chrome based dev desktop worked fine, mainly due to my habit of hitting ctrl/f5 which clears the cache before loading.

However, on my android mobile, this needs an explicit cache clear before loading the page e.g. via the settings/history etc., which I was not in the habit of doing. Therefore when testing the new version on mobile, it failed. The new version was a production build which did not have source maps. The old version was a dev one with source maps. The console errors indicated an inability to find and load the source maps, which were not present in the latest prod deployment, but were still being requested by the cached scripts on the mobile. An explicit cache clear solved the problem. 

When I tried this again on the desktop without the cache clear, i.e. with a straight refresh, I was able to duplicate the error seen on the android chrome.

Note that the problem only applies to the fabric deployment as all the others have a timestamp (ts=) querystring parameter added to the scripts when loading, which prevents caching. I normally disable the output hashing on all microapps including the fabric for convenience, to avoid editing the app config to change the names of all the dynamically loaded scripts, even for a prod build.

Doing this on the Fabric is unnecessary as the scripts are not loaded dynamically by my code, so if I left the hashing on for the fabric, this would solve the problem, as the timestamp querystring would sort the issue for all the others.

Going forward, I intend to improve automation of the build process for hosted deployments by providing e.g. an NPM script to build all the microapps into a single zipped deployment which can be just uploaded and unzipped – at present I am having to do each one individually and copy up the dist directory. Whilst all this is only a POC application, I am doing it often enough to make this worthwhile so as not to waste time or make mistakes with it.

Doing this would also allow the possibility of using my own timestamps on all the filenames for every script, and dynamically modifying the config to incorporate the timestamps in the build script. This would allow caching to be left enabled for the code scripts (but not for the config), which would actually be a benefit as it would improve startup times and lessen bandwidth usage – especially relevant for mobiles. I could add a fabric config parameter to indicate whether the querystring timestamps should be added or not when loading scripts, so that the feature can still be used when needed. Perhaps I could leave this enabled for dev mode and not bother with timestamps on the actual filenames for example.

Using my own timestamps on the names would simplify and standardise the names, as I could use an identical timestamp on all scripts for every microapp done as part of a given build, to simplify naming and build automation – I would only need to pass around one single timestamp parameter which would be used for all generated script files and in the config.

 

Comments Off on Caching of old microapp scripts breaks new version

June 22nd, 2022
3:03 pm
PrimeNG V13 Chart Axes Bleedover Issue

Posted under Angular & Knowledge Base & PrimeNG & Web
Tags , ,

Unfortunately with V13 of PrimeNG and its supported chart.js versions, the axes do not get removed when switching  chart types and bleed onto the next chart. It appears that the chart must be destroyed and recreated when changing types – a refresh or reinit call are not enough, and explicit angular change detector calls don’t work either, see here.

Therefore as just having a single permanent chart and changing its properties fails as above, I’m explicitly making the chart data undefined to ensure that the *ngIf in the template destroys and recreates the chart. This works fine.

This appears to be a new bug.

Comments Off on PrimeNG V13 Chart Axes Bleedover Issue

June 8th, 2022
11:11 am
PrimeNG V13 Panel Header Text Not Centering

Posted under Angular & Knowledge Base & PrimeNG & Web
Tags , ,

I hit an issue where a PrimeNG panel, used in a Dataview component in grid mode, was not centering its header text after upgrading to the latest V13.

The issue was weird – the header container div was set to flex by the Panel component itself, but when I used justify-content: center to center the text in a dependent span, it remained left justified despite all my efforts to check for other style issues, force styling with !important etc.

When I checked the latest PrimeNG showcase online, I noticed that this also has the same problem. I noted that other containers/elements in the Panel such as the content container div did not use flex, so I felt happy to force the styling for the container back to block and use text-align: center instead. I did this locally in the affected microapp component rather than the overall theme, but might rethink this later and standardize if I hit the issue again.

This solved the problem and the text centered correctly.

Comments Off on PrimeNG V13 Panel Header Text Not Centering