Developing Angular PWAs with a focus on microfrontends

I have set up multiple microfrontends using an "app-shell" type of application for the domain root, with each microfrontend on the first path element. Each app is constructed as a standalone angular application utilizing shared libraries to reuse common components and functionalities. They are contained in their own separate docker container and connected through nginx proxy maps.

mydomain.com/         <- domain root app
            /child1   <- micro apps 
            /child2
            /child3
            /child4

I am looking to implement an angular service worker to cache all our resources.

Initial approach

The initial approach was to have one service worker per app. This means that the domain-root app had one, as well as each of our "child-apps." There was one ngsw-config.json file per standalone angular app.

Issue

This approach led to conflicts between two service workers controlling the caching in each child-app, resulting in users potentially seeing an older version depending on which service worker won.

Second attempt

The second attempt involved having one service worker to manage all apps. It was placed on the domain-root app, with the aim of caching every "child-app's" resources by using ngsw-config.json's assetGroup.resources.files or assetGroup.resources.urls. However, this did not seem to effectively map the resources of the child-apps into the cache storage. Although the network tab indicated that the resource files were being served from the ServiceWorker. 🤔

Problem

The issue here is that the apps no longer notify when a new version is deployed. This may be due to the angular-cli not recognizing the names of compiled resources during build time. These resources have to be cached at runtime. Additionally, there is uncertainty about whether any resources are being cached at all, as offline mode does not yield any response upon toggling.

Furthermore, there are instances where an older version of the domain-root app is loaded initially, requiring a page refresh to access the updated version. Code has been inserted to remove the second service worker from the initial attempt. The following code removes all service workers registered to mydomain.com/childX:

// Force remove old redundant service workers
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.getRegistrations().then(registrations => {
    registrations
      .filter(r => 'scope' in r && r.scope !== `${location.protocol}//${location.host}/`)
      .forEach(r => r.unregister());
  });
}

Another observation involves Cache storage in DevTools/Application, showing exactly 4 entries per new version hash, retaining all versions instead of overwriting the cache with each new version.

Current setup:

Using @angular/service-worker: 10.1.4

ngsw-config.json

    
{
      "$schema": "./node_modules/@angular/service-worker/config/schema.json",
      "index": "/index.html",
      "assetGroups": [
        {
          "name": "root",
          "installMode": "prefetch",
          "updateMode": "prefetch",
          "resources": {
            "files": [
              "/assets/icons/favicon.ico",
              "/index.html",
              "/manifest.webmanifest",
              "/*.css",
              "/*.js",
              "/*.txt"
            ]
          }
        },
        {
          "name": "apps",
          "installMode": "lazy",
          "updateMode": "prefetch",
          "resources": {
            "files": [
              "/**/assets/icons/favicon.ico",
              "/**/index.html",
              "/**/manifest.webmanifest",
              "/**/*.css",
              "/**/*.js",
              "/**/*.txt"
            ]
          }
        },
        {
          "name": "root-assets",
          "installMode": "prefetch",
          "updateMode": "prefetch",
          "resources": {
            "files": [
              "/assets/**",
              "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
            ]
          }
        },
        {
          "name": "apps-assets",
          "installMode": "lazy",
          "updateMode": "prefetch",
          "resources": {
            "files": [
              "/**/assets/**",
              "/**/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
            ]
          }
        }
      ]
    }

app.component.ts

  
constructor(updates: SwUpdate) {
    // Ensure running on an updated version
    if (updates.isEnabled) {
      // Prompt user for permission to update upon registration of a new update
      updates.available.subscribe(() => {
        if (confirm('A newer version of the application is available. Load the new version?')) {
          updates.activateUpdate().then(() => document.location.reload());
        }
      });
      // Check for updates every 5 minutes
      timer(0, 5 * 60 * 1000).subscribe(n => updates.checkForUpdate());
    } else {
      console.log('SW not enabled!');
    }
  }

Question

How should the configuration be adjusted to ensure proper caching of resources by the service worker(s?) and prevent them from conflicting with each other (if individual service workers per app are necessary)?

EDIT

For those encountering similar challenges; this issue could potentially be resolved by leveraging the new Module Federation system introduced in Webpack 5, expected to be released with Angular 11 (https://www.angulararchitects.io/aktuelles/the-microfrontend-revolution-part-2-module-federation-with-angular/).

Answer â„–1

Dealing with a similar issue, I managed to find a solution by sending a json file to the main application containing the versions of the sub-apps. Additionally, I implemented caching for the sub-apps based on their hashed bundles.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Delete the text in MUI's TablePagination component that displays the number of rows per page and the total rows in the table

Currently, I am integrating MUI's tablePagination component into my React table with TypeScript. I am looking to remove/disable the circlemarked text displayed in the image (the picture is an example from MUI). https://i.stack.imgur.com/ib0t2.png Af ...

Using TypeScript: Union Types for Enum Key Values

Here's the code in the TS playground too, click here. Get the Enum key values as union types (for function parameter) I have managed to achieve this with the animals object by using key in to extract the key as the enum ANIMALS value. However, I am s ...

One cannot use a type alias as the parameter type for an index signature. It is recommended to use `[key: string]:` instead

I encountered this issue in my Angular application with the following code snippet: getLocalStreams: () => { [key: Stream['key']]: Stream; }; During compilation, I received the error message: An index signature parameter typ ...

Create a tuple type that encompasses all possible paths within a recursive object configuration

Consider the following templates for 'business' types, representing compound and atomic states: interface CompoundState<TName extends string, TChildren extends { [key: string]: AnyCompoundState | AnyAtomicState }> { type: 'parent&ap ...

Encountering a node module issue when implementing graphql in a TypeScript project

I encountered issues when attempting to utilize the @types/graphql package alongside TypeScript Node Starter node_modules//subscription/subscribe.d.ts(17,4): error TS2314: Generic type AsyncIterator<T, E>' requires 2 type argument(s). node_modu ...

iOS 10.3.1 causing Ionic 2 (click) event to trigger twice

I am currently working on an Ionic 2 app and I am facing an issue with the click event. When I test the app on a device and click on a button, let's say to trigger an alert, the function executes once. However, if I click on the button again, the fun ...

Exploring Angular 4.0: How to Loop through Numerous Input Fields

I am looking to loop through several input fields that are defined in two different ways: <input placeholder="Name" name="name" value="x.y"> <input placeholder="Description" name="description" value"x.z"> <!-- And more fields --> or lik ...

Discover the combined type of values from a const enum in Typescript

Within my project, some forms are specified by the backend as a JSON object and then processed in a module of the application. The field type is determined by a specific attribute (fieldType) included for each field; all other options vary based on this ty ...

Looping Through RxJS to Generate Observables

I am facing the challenge of creating Observables in a loop and waiting for all of them to be finished. for (let slaveslot of this.fromBusDeletedSlaveslots) { this.patchSlave({ Id: slaveslot.Id, ...

Transform the MUI Typescript Autocomplete component to output singular values of a specific property rather than a complete object

When utilizing MUI Autocomplete, the generic value specified in onChange / value is determined by the interface of the object set in the options property. For instance, consider an autocomplete with the following setup: <Autocomplete options={top ...

Handling HTTP Client Errors in Angular 4.x

I am currently working on developing an application that utilizes Angular for the front end and Laravel 5.0 for the back end. One of the challenges I am facing is handling errors in HTTP requests. Here is an example from my MenuController.php, where I pr ...

Combine various arrays of objects into one consolidated object

Problem: There are untyped objects returned with over 100 different possible keys. I aim to restructure all error objects, regardless of type, into a singular object. const data = [ { "type":"cat", "errors" ...

Why does Typescript not enforce a specific return type for my function?

In my custom Factory function, I need to return a specific type: type Factory<T> = () => T; interface Widget { creationTime: number; } const createWidget: Factory<Widget> = () => { return { creationTime: Date.now(), foo: &a ...

Tips on updating the datepicker format to be dd/mm/yyyy in ngbdatepicker

I am currently using ng-bootstrap for a datepicker and need to change the date format from yyyy/mm/dd to dd/mm/yyyy. I have tried to make this adjustment but haven't had success. If anyone has suggestions on how to accomplish this, please help. Here ...

Tips for presenting retrieved HTML unchanged within an Angular 2 template

Currently, I am fetching post data from a remote server. The content of the posts includes HTML with style and class attributes which are generated by a WYSIWYG editor. My goal is to display the HTML data as it is, without any filtering or sanitization. ...

What is the reason that the protected keyword is not retained for abstract properties in TypeScript?

I'm uncertain whether this issue in TypeScript is a bug or intended functionality. In my Angular project, I have 3 classes - an abstract service, a service that implements the abstract service, and a component that utilizes the implementing service. ...

Navigating through an array to extract necessary information within an Angular framework

Below is the JSON data I have: [{ "_id": 1, "Name": "x", "Email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a6dce6c3d6d5cfcac9c888c5c9cb">[email protected]</a> ", "Designation": "Manager", "Projec ...

List in Angular remains empty

I am facing an issue with populating a simple HTML list using Angular. The problem arises when trying to display the data in the list. When I use console.log("getUserCollection() Data: ", data);, it shows an array which is fine, but console.log("getUser() ...

Breaking down an object using rest syntax and type annotations

The interpreter mentions that the humanProps is expected to be of type {humanProps: IHumanProps}. How can I properly set the type for the spread operation so that humanPros has the correct type IHumanProps? Here's an example: interface IName { ...

What's the best way to set up multiple NestJS providers using information from a JSON file?

Recently diving into NestJS, I am in the process of building an application following the MVC architecture with multiple modules including: Project | +-- App.Controller +-- App.Service +-- App.Module | +-- SubModule1 | | | +-- SubModule1.C ...