How to link Angular project dist directory to PlatformIO SPIFFS data directory

For tips how to make the Angular build small enough to fit into the SPIFFS image, see How to make Angular work with ESP32 SPIFFS / ESPAsyncWebserver

When you are building a PlatformIO image, you can easily make the dist/[project_name] directory from the Angular project directory appear in the SPIFFS image by using a symlink.

My config tells the server to serve from the www subdirectory.

server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.html");

Therefore, we first need to create the data directory in the same directory where platformio.ini is located:

mkdir data

Now we can create a symlink from the angular dist directory to data/www, for example:

ln -s ../MyUI/dist/myui data/www

PlatformIO will automatically handle the symlink, if the directory exists.

Posted by Uli Köhler in Angular, ESP8266/ESP32, PlatformIO

How to make Angular work with ESP32 SPIFFS / ESPAsyncWebserver

The main issue when using Angular web UIs is that the resulting files get too large, hence building the filesystem image will fail with SPIFFS_write error(-10001): File system is full.

Using these tips, I could get an Angular PrimeNG app to fit into a 4MB flash ESP32 module without any custom partition table and without any other crazy hacks! Even the fonts & PrimeNG icons fit into the SPIFFS easily, with a total of only 380 kB of the approximately 1.5 MB being consumed.

File compression

The number one most important tip is that you can just gzip -9 the files from the angular dist directory and ESPAsyncWebserver will automatically handle decompressing them!

This is my platformio.ini:

platform = espressif32
platform_packages = framework-arduinoespressif32 @
board = esp32dev
framework = arduino
board_build.filesystem = littlefs
lib_deps =
    esphome/[email protected]^1.2.2
    esphome/[email protected]^2.1.0
    [email protected]
upload_speed = 460800
monitor_speed = 115200

This is my angular build script:

ng build --aot --build-optimizer --optimization --progress --output-hashing none
gzip -9 dist/**/*

This is where I tell ESPAsyncWebserver (note that you should use the esphome fork) to serve files statically:

server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.html");

Other tips

In order to make your life easier managing the data directory with both Angular files and other files, see How to link Angular project dist directory to PlatformIO SPIFFS data directory

You can use purgecss but compression works so well that it isn’t really worth both the risk of accidentally removing some CSS rules which you manually need to whitelist. Before discovering how well compression worked, I started to manually remove CSS rules from the PrimeNG theme file. This worked fine, but the SPIFFS still wasn’t small enough.

Often you can save space by deleting.

For example, primeicons.svg and primeicons.ttf are two different formats with the same content. Note that some (especially older, and some mobile) browsers don’t support all formats, hence it’s rather risky to remove them if you need to support multiple platforms.

Posted by Uli Köhler in Angular, ESP8266/ESP32, PlatformIO

How to remove hash from Angular “ng build” filenames

Angular generates filenames like Inter-Light.27083fa6375bb9ef.woff2 in the dist folder when building for production using ng build.

These hashes have the purpose of preventing the files from being cached, so if you remove the hash, you will need to find some other way of preventing caching

You can disable the hashes by using

--output-hashing none

as an argument to ng build.

Full ng build example:

ng build --aot --build-optimizer --optimization --progress --output-hashing none


Posted by Uli Köhler in Angular

How to use optional type in Angular / TypeScript

If you have a variable called ws of type WebSocket, you can not assign null or undefined to that variable

In order to make a variable that is either of the given type or undefined, use the following syntax:

ws?: WebSocket = undefined;


Posted by Uli Köhler in Angular, Javascript

How to configure Angular ‘ng serve’ API proxy

In order to proxy /api to http://localhost:62232 for example, first create proxy.conf.json in the same directory where package.json is located:

        "target": "http://localhost:62232",
        "secure": false

Now we need to modify package.json. Locate the line where ng serve is called, such as:

"start": "ng serve",

and add --proxy-config proxy.conf.json to the arguments of ng serve:

"start": "ng serve --proxy-config proxy.conf.json",

Full example for the scripts section of package.json:

"scripts": {
  "ng": "ng",
  "start": "ng serve ng serve --proxy-config proxy.conf.json",
  "build": "ng build --configuration=production",
  "watch": "ng build --watch --configuration development",
  "test": "ng test"


Posted by Uli Köhler in Angular, Javascript

Angular: ActivatedRoute minimal example

For this route:

{path: 'my/:id', component: MyDashboardComponent},

this is how you can use it in MyDashboardComponent:

constructor(private route: ActivatedRoute) {
      this.route.params.subscribe(params => {


Posted by Uli Köhler in Angular, Javascript

How to fix Angular Service ngOnInit() not being called


You have an Angular service implementing OnInit:

import { Injectable, OnInit } from '@angular/core';

export class MyService implements OnInit {

  constructor() { }
  ngOnInit() {
    console.log("MyService initializing");

but it never prints MyService initializing – i.e. the ngOnInit() function is never actually being called.


Services should not implement OnInit, the function is deliberately never called. Instead, add the code from ngOnInit() in the constructor() and remove implements OnInit and ngOnInit():

import { Injectable, OnInit } from '@angular/core';

export class MyService {

  constructor() {
    console.log("MyService initializing");


Posted by Uli Köhler in Angular, Typescript

How to fix Angular HttpClient toPromise() deprecated (rxjs)


You have angular HTTP client code like


but toPromise() is deprecated in recent versions of angular / rxjs.

/** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: */
toPromise(): Promise<T | undefined>;


In most cases, for HttpClient, you want to use rxjs’s firstValueFrom() since the HttpClient Observables typically only return one value anyway.

First, import firstValueFrom from rxjs:

import { firstValueFrom } from 'rxjs';

then remove the .toPromise() call:

// Before
// After

and surround the entire statement with firstValueFrom:

// Before
// After

This will fix the issue.

Posted by Uli Köhler in Angular, Typescript

Angular HTTPClient ReplaySubject example without query parameters

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ReplaySubject } from 'rxjs';
import { MyType } from './my-type';

export class MyService {
  baseURL = "http://localhost:18674";

  // Replay subject: New subscribers will get previous values
  public nodes = new ReplaySubject<MyType>();

  constructor(private http: HttpClient) {
    // Currently only acquire nodes once
    this.http.get<MyType>(`${this.baseURL}/api/myapi`).subscribe(value =>;


Posted by Uli Köhler in Angular, Javascript, Typescript

How to fix Angular ng: not found


When trying to run a command like ng build, e.g. in a CI setup, you see an error message like

sh: ng: not found

Preferred solution:

Only the machine where you’re running this command, you have not installed the ng command globally.

However, your node_modules folder will have a local installation of @angular/cli.

In order to use it, replace ng by ./node_modules/.bin/ng.

For example, a scripts section of your package.json

"scripts": {
  "build": "ng build"

would be replaced by

"scripts": {
  "build": "./node_modules/.bin/ng build"

Alternative solution:

Install ng globally on the machine running the command by using

sudo npm i -g @angular/cli


Posted by Uli Köhler in Angular

Best practice Angular 11 production build command

This is the command I use to make production builds of Angular 11+ webapps:
ng build --prod --aot --build-optimizer --common-chunk --vendor-chunk --named-chunks


While it will consume quite some CPU and RAM during the build, it will produce a highly efficient compiled output.

Posted by Uli Köhler in Angular, Javascript

How to fix Angular 9 @ViewChild Expected 2 arguments, but got 1: An argument for ‘opts’ was not provided.


You are trying to compile your Angular 9.x application, but you see an error message like

app/my-component/my-component.component.ts:24:4 - error TS2554: Expected 2 arguments, but got 1.

24   @ViewChild(MyOtherComponent) myOtherComponent: MyOtherComponent;

    7888     (selector: Type<any> | Function | string, opts: {
    7889         read?: any;
    7890         static: boolean;
    7891     }): any;
    An argument for 'opts' was not provided.


Find this line in your code at the location specified in the error message:

@ViewChild(MyOtherComponent) myOtherComponent: MyOtherComponent;

and add

{static: false}

as second argument to the @ViewChild() declaration:

@ViewChild(MyOtherComponent, {static: false}) myOtherComponent: MyOtherComponent;

In most cases, you want to use static: false. See this post on StackOverflow for details on when to use static: true as opposed to static: false.

Posted by Uli Köhler in Angular, Javascript, Typescript

How to fix ‘ng upgrade’ error ‘The specified command (“upgrade”) is invalid’


You want to run ng upgrade to update your angular project libraries, but you see this error message:

The specified command ("upgrade") is invalid. For a list of available options,
run "ng help".

Did you mean "update"?


You need to run

ng update

instead of ng upgrade (which is not a valid command).

This is what Did you mean "update"? in the error message is intended to point you towards.

Posted by Uli Köhler in Angular, Javascript

Fixing Angular Error: Unexpected value ‘undefined’ declared by the module


For a module of yours, you get an error message like this on load:

Error: Unexpected value 'undefined' declared by the module 'MyModule'
    at syntaxError (compiler.js:1021)
    at compiler.js:10623
    at Array.forEach (<anonymous>)
    at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver.getNgModuleMetadata (compiler.js:10621)
    at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler._loadModules (compiler.js:23876)
    at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler._compileModuleAndComponents (compiler.js:23857)
    at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler.compileModuleAsync (compiler.js:23817)
    at CompilerImpl.push../node_modules/@angular/platform-browser-dynamic/fesm5/platform-browser-dynamic.js.CompilerImpl.compileModuleAsync (platform-browser-dynamic.js:143)
    at core.js:4999


First, try to restart ng serve. In some cases this will outright fix the issue.

The error message is caused by some element of your @NgModule declarations: [ /* ... */ ] to be undefined (… declared by the module in the error message should be taken literally, it’s in the declarations!).

For example, if you have a @NgModule declaration like this:

  imports: [
  declarations: [,
  providers: []

The issue is in the stray comma in the declarations line: When compiled, it results in [undefined, MyDetailComponent, MySearchComponent]. Removing the stray comma fixes the issue.

Another possible cause of this error is if one of the values in declarations is undefined.

For example if you have an import statement like

import { MyComponent } from './my.component';

in your module file, and, for some reason, MyComponent is undefined, this will cause the same error message to appear.

In order to track down which of your components in the declarations array, you could temporarily add a statement like this before your @NgModule :

if(MyComponent === undefined) {
    console.log("MyComponent is undefined!");

In case you don’t have any of the issues above, follow the standard procedure:
Comment out every element in your declarations array and see which causes the error message to disappear.

Posted by Uli Köhler in Angular, Javascript

How to fix Angular6 “_getAriaLabel is not a function” with production build


You are building an Angular6 application and in development mode everything works fine. However, if you build in production mode:

ng build --prod --aot

you see an error like this in the client:

main.4d1baabffbba5677af03.js:1 ERROR TypeError: i.ɵnov(...)._getAriaLabel is not a function
    at Object.updateRenderer (main.4d1baabffbba5677af03.js:1)
    at Object.updateRenderer (main.4d1baabffbba5677af03.js:1)


The issue appears to be caused by incorrectly updated NodeJS modules. You can fix it by simply deleting your node_modules folder:

rm -rf node_modules

Furthermore, it’s recommended to update @angular/cli as the bug does not seem to be present in newer versions of @angular/cli:

sudo npm i -g @angular/cli

Source & discussion on GitHub

Posted by Uli Köhler in Angular, Javascript

How to fix Angular4/5/6 Unexpected token ‘px’

If you encounter an error message like this:

Parser Error: Unexpected token 'px' at column 3 in [70px] in ng:///AppModule/[email protected]:34 ("="let string of strings">

look at the line the error is referring to. It will look similar to this:

<mat-expansion-panel-header [collapsedHeight]="70px">

You have two options of fixing this:

Option 1: Recommended if the value (70px in this case) is always constant.

Remove the brackets from the attribute: [collapsedHeight] to collapsedHeight. The brackets mean that the value shall be interpreted as Javascript and removing them means interpreting the value as attribute. You code should look like this:

<mat-expansion-panel-header collapsedHeight="70px">

Option 2: Force angular to interpret the value (70px in this case) as a string:

Add single quotes before and after the value makes it valid Javascript:

<mat-expansion-panel-header [collapsedHeight]="'70px'">

I recommend to use this option only if you expect the value to be a non-constant javascript expression in the future.

Posted by Uli Köhler in Angular, Javascript

How to fix Angular ‘TypeError: templateRef.createEmbeddedView is not a function’


You encounter an error message like this:

ERROR TypeError: templateRef.createEmbeddedView is not a function
    at ViewContainerRef_.createEmbeddedView (core.js:11389)
    at NgIf._updateView (common.js:2843)
    at NgIf.set [as ngIfElse] (common.js:2815)
    at updateProp (core.js:12602)
    at checkAndUpdateDirectiveInline (core.js:12313)
    at checkAndUpdateNodeInline (core.js:13876)
    at checkAndUpdateNode (core.js:13819)
    at debugCheckAndUpdateNode (core.js:14712)
    at debugCheckDirectivesFn (core.js:14653)
    at Object.eval [as updateDirectives] (MyComponent.html:1)

in a component where you have a source code similar to this

<div *ngIf="myCondition ; else elseSection">
    <!-- ... -->
<div #elseSection>
    <!-- ... -->


Whatever element you reference in the *ngIf else clause can’t be any arbitrary component, but it must be a ng-template.

In order to solve this, change <div #elseSection> to <ng-template #elseSection>. Note that using just <template> is deprecated since Angular4.

The resulting source code should look like this:

<div *ngIf="myCondition ; else elseSection">
    <!-- ... -->
<ng-template #elseSection>
    <!-- ... -->
Posted by Uli Köhler in Angular, Javascript

How to fix Angular ‘Cannot find control with unspecified name attribute’


In your Angular2/4/5 application you see this error message:

Cannot find control with unspecified name attribute


Look for a statement in the HTML angular template like this:


The error message means that myCtrl can’t be found. Check if this variable is present in your class – it needs be a FormControl which you can import from @angular/forms:

import { FormControl } from '@angular/forms';

In my case, changing it to


fixed the issue

Posted by Uli Köhler in Angular, Javascript, Typescript

How to fix Angular4/5/6 ‘No provider for ControlContainer’


In your Angular2/4/5 application you’re getting the following error message:

No provider for ControlContainer ("<div class="recall-container mat-elevation-z8">


You have not added the @angular/forms FormsModule to your module’s import list.

Go to your app.module.ts and add this line to the imports:

import { FormsModule } from '@angular/forms';

and look for a line like this in your module definition:

imports: [ /* several import modules may be listed here */ ],

and add FormsModule like this (if there are already imports, add FormsModule to the list):

imports: [ FormsModule ],
Posted by Uli Köhler in Angular, Javascript, Typescript