0

I have created a PWA with Angular 6. Later I want to have different Icons and start Urls, since the app will be running under multiple urls (each account will be assigned a unique url and each of them have different logos). So I want to change the manifest.json dynamically.

Is there a way to do that?

Edit:

I try it like this:

<head>
  <meta charset="utf-8">
  <title>Pwatest</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="assets/icons/coffee.png">
  <link rel="manifest" id="my-manifest-placeholder">
  <link rel="apple-touch-icon" href="assets/icons/icon-192x192.png">
  <!-- <link rel="manifest" href="manifest.json"> -->
  <meta name="theme-color" content="#1976d2">

  <script>
  var test = true;

  var myDynamicManifest = {
    "name": "pwatest",
    "short_name": "pwatest",
    "theme_color": "#1976d2",
    "background_color": "#fafafa",
    "display": "standalone",
    "scope": "/",
    "start_url": "/",
    "icons": []
  }

  if(test){
    myDynamicManifest['icons'].push({
            "src": "assets/icons/coffee-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
    });
  }else{
    myDynamicManifest['icons'].push({
            "src": "assets/icons/frog-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
    });
  }

  console.log(myDynamicManifest);
  const stringManifest = JSON.stringify(myDynamicManifest);
  const blob = new Blob([stringManifest], {type: 'application/json'});
  const manifestURL = URL.createObjectURL(blob);

  document.querySelector('#my-manifest-placeholder').setAttribute('href', manifestURL);
  console.log(document.querySelector('#my-manifest-placeholder'));
  </script>

</head>

output

Edit2:

I don't found a solution. I try it like this now:

index.html

<link rel="manifest" id="my-manifest-placeholder" href="/manifest.php">

manifest.php

<?php
$test = [
    "name" => "pwatest",
    "gcm_user_visible_only" => true,
    "short_name" => "pwatest",
    "start_url" => "/",
    "display" => "standalone",
    "orientation" => "portrait",
    "background_color" => "#fafafa",
    "theme_color" => "#1976d2",
    "icons" => [
        "src" => "assets/icons/coffee-192x192.png",
        "sizes"=> "192x192",
        "type" => "image/png"
    ]
];

header('Content-Type: application/json');
echo json_encode($test);
?>

but I become the full php file not a JSON.

can I do it like this?

vDrews
  • 155
  • 5
  • 14

3 Answers3

2

You can create a separate manifest.json for each configuration and let the build process move it to the document root.

  1. Create different files (in this example for dev and prod) under
  • src/environment/dev/manifest.json
  • src/environment/prod/manifest.json
  1. Add the following to your angular.json for the different configurations (e.g. after fileReplacements):
"assets": [
  "src/assets",
  {
    "input": "src/environments/dev",
    "output": "/",
    "glob": "manifest.json"
  }
],
"assets": [
  "src/assets",
  {
    "input": "src/environments/prod",
    "output": "/",
    "glob": "manifest.json"
  }
],
  1. Remove you original manifest.json
2

We could either have the manifest file/ manifest.json file.

I've created environments folder that contains the 2 environments used, prod and qa. Each folder contains the environment file as well as the manifest files corresponding to the environment.

I've also added an environment file to be used locally. This will be replaced with the respective environment files based on the build configuration.

Folder Structure

QA Environment

environment.qa.ts

export const environment = 
{
    appVersion: require('../../package.json').version,
    production: true,
    title:'Sample App',
    serverUrl: 'https://sampleapp/api/'
};

manifest.webmanifest

{
  "name": "Sample App QA",
  "short_name": "Sample App QA",
  "theme_color": "#1976d2",
  "background_color": "#fafafa",
  "display": "standalone",
  "scope": "./",
  "start_url": "./",
  "icons": [
    {
      "src": "assets/icons/qa/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "assets/icons/qa/icon-88x88.png",
      "sizes": "88x88",
      "type": "image/png",
      "purpose": "maskable any"
    }
  ]
}

prod will also have both files configured to use the production environment.

angular.json

 "production": {
      "assets": [
        "src/favicon.ico",
        "src/assets",
        {
          "input": "src/environments/prod",
          "output": "/",
          "glob": "*.webmanifest"
        }
      ],
      "budgets": [
        {
          "type": "initial",
          "maximumWarning": "100kb",
          "maximumError": "200kb"
        },
        {
          "type": "anyComponentStyle",
          "maximumWarning": "100kb",
          "maximumError": "500kb"
        }
      ],
      "fileReplacements": [
        {
          "replace": "src/environments/environment.ts",
          "with": "src/environments/prod/environment.prod.ts"
        }
      ],
      "outputHashing": "all"
    },
    "qa": {
      "assets": [
        "src/favicon.ico",
        "src/assets",
        {
          "input": "src/environments/qa",
          "output": "/",
          "glob": "*.webmanifest"
        }
      ],
      "budgets": [
        {
          "type": "initial",
          "maximumWarning": "1.5mb",
          "maximumError": "2.5mb"
        },
        {
          "type": "anyComponentStyle",
          "maximumWarning": "100kb",
          "maximumError": "500kb"
        }
      ],
      "fileReplacements": [
        {
          "replace": "src/environments/environment.ts",
          "with": "src/environments/qa/environment.qa.ts"
        }
      ],
      "outputHashing": "all"
    },

The environment files as well as manifest files will be replaced based on environment specified at the time of build.

ng build --prod --configuration=qa--source-map

The command will build the app based on the data from manifest file and environment files under the folder qa in environments.

Kezia Rose
  • 365
  • 1
  • 2
  • 12
1

You can achieve this by serving the manifest through a nodejs service (or your language of choice) and dynamically outputting the response to the GET manifest.json request.

I've done this in the past based on the "Host" header, which will tell you which domain the manifest is being requested for.

For example, an express request handler for this might look something like:

app.get("/manifest.json", function (req, res) {
    const host = req.header("Host")
    const manifest = buildManifestForHost(host)

    res
        .status(200)
        .send(manifest)
})

function buildManifestForHost(host: string): object {
    // build your manifest.json response here
}
Michael
  • 2,189
  • 16
  • 23
  • Added an example of how you might do this in an Express NodeJS service – Michael Jan 30 '20 at 08:48
  • I edit an example I want to do it, but I try your's two and comment again – vDrews Jan 30 '20 at 08:53
  • I'm not sure whether your client side example would work, but even if it does work I'd still recommend doing this server side to avoid leaking information about all the different deployments, and to avoid shipping unnecessary code/data to the browser. – Michael Jan 30 '20 at 08:57
  • is your code in an extra component or where I have to place the code? – vDrews Jan 30 '20 at 09:03
  • It would be a request handler on a NodeJS express service - it's independent of the Angular code. See https://expressjs.com/ or https://angular.io/guide/universal – Michael Jan 30 '20 at 09:49
  • The client side example works, if the manifest.json is in the dist folder, but not from another url. Then all time I become the error: ignored should be same origin as document. I try yours but something must be wrong, it's not working – vDrews Feb 06 '20 at 10:45