Javascript

How to fix Angular NullInjectorError: No provider for HttpClient!

In case you see the following error for your Angular application in your JS console:

main.ts:6 ERROR NullInjectorError: R3InjectorError(AppModule)[MyService -> HttpClient -> HttpClient -> HttpClient]: 
  NullInjectorError: No provider for HttpClient!
    at NullInjector.get (core.mjs:7599:27)
    at R3Injector.get (core.mjs:8020:33)
    at R3Injector.get (core.mjs:8020:33)
    at R3Injector.get (core.mjs:8020:33)
    at injectInjectorOnly (core.mjs:634:33)
    at Module.ɵɵinject (core.mjs:638:60)
    at Object.MyService_Factory [as factory] (my.service.ts:8:38)
    at R3Injector.hydrate (core.mjs:8121:35)
    at R3Injector.get (core.mjs:8009:33)
    at ChainedInjector.get (core.mjs:12179:36)

you need to add HttpClientModule to your app.module.ts.

First, import HttpClientModule at the top of app.module.ts:

import { HttpClientModule } from '@angular/common/http';

After that, add

HttpClientModule,

to the imports: [...] section of your @NgModule, for example:

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    HttpClientModule,
    BrowserModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

After that, your error should have disappeared

Posted by Uli Köhler in Angular, Typescript

Angular HttpClient JSON service minimal example

Using Angular’sHttpClient  in a service to fetch a JSON is pretty straightforward. In this example, we’ll fetch a JSON array.

Note that it’s recommended to create your own TypeScript interface for improved static typing, but nevertheless starting with any[] is often a good idea since it allows you to get started quickly.

public loadMyJSONArray(): Observable<any[]> {
  return this.http.get<any[]>(`/api/my-api`);
}

Full service example

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class MyService {

  constructor(private http: HttpClient) {
  }

  public loadMyJSONArray(): Observable<any[]> {
    return this.http.get<any[]>(`/api/my-api`);
  }

}

 

 

Posted by Uli Köhler in Angular, Typescript

How to escape double quotes (“) in stirng

Note: This solution does not deal with double quotes which are escaped already correctly, plus it does not correctly escape backslashes. However it can still serve as a starting point for doing customized escaping.

'foo"bar"gas'.replace(/"/g, "\\\"") # Results in foo\"bar\"gas

 

Posted by Uli Köhler in Javascript

Remove all slashes from string using pure Javascript

"foo/bar/gas".replace(/\//g, "") # foobargas

 

Posted by Uli Köhler in Javascript

Simple tsconfig.json template for NodeJS

Use this tsconfig.json as a starting point to setup your NodeJS TypeScript project:

{
  "compilerOptions": {
    "strict": false,
    "target": "es2020",
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "skipLibCheck": true,
    "outDir": "./build",
    "rootDir": "./",
    "lib": [
      "esnext"
    ],
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "**/*.ts",
    "types/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

 

Posted by Uli Köhler in NodeJS, Typescript

How to import @koa/router in Typescript

import Router from '@koa/router';
import Koa from 'koa';

const app = new Koa();
const router = new Router();

 

Posted by Uli Köhler in NodeJS, Typescript

How to import Koa in Typescript

import Koa from 'koa';

const app = new Koa();

/* TODO your code goes here */

 

Posted by Uli Köhler in NodeJS, Typescript

Minimal Koa Typescript example using @koa/router and koa-body

This example showcases how to make a simple Hello World server using Koa in Typescript.

import Router from '@koa/router';
import koaBody from 'koa-body';
import Koa from 'koa';

const router = new Router();
const app = new Koa();

app.use(koaBody());

router.get('/', (ctx)  => {
    ctx.body = "Hello world";
});

app
    .use(router.routes())
    .use(router.allowedMethods());

if (!module.parent) app.listen(3000);
{
  "compilerOptions": {
    "strict": true,
    "target": "es2020",
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "skipLibCheck": true,
    "outDir": "./build",
    "rootDir": "./",
    "lib": [
      "esnext"
    ],
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "**/*.ts",
    "types/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}
{
  "name": "KoaTypescriptTest",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "start": "./node_modules/.bin/tsnd --respawn server.ts",
    "build": "./node_modules/.bin/tsc -b tsconfig.json"
  },
  "dependencies": {
    "@koa/router": "^12.0.0",
    "koa": "^2.14.1",
    "koa-body": "^6.0.1"
  },
  "devDependencies": {
    "@types/koa": "^2.13.5",
    "@types/koa__router": "^12.0.0",
    "@types/node": "^18.11.17",
    "ts-node-dev": "^1.1.8",
    "typescript": "^4.9.4"
  }
}

 

Posted by Uli Köhler in NodeJS, Typescript

How to auto-fill & auto-submit Google Search Console sitemap using Tapermonkey

When using Google Search Console, you might want to submit your sitemap manually from time to time to accelerate indexing of new content on your website.

The following Tapermonkey script will automatically fill in sitemap.xml and click the Send button. Depending on your language setting, you will need to adjust the document.querySelector() calls and modify the script header according to your domain (mydomain.com in this example):

// ==UserScript==
// @name         SearchConsoleAutoFillSitemap
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  ?
// @author       You
// @match        https://search.google.com/search-console/sitemaps?resource_id=https%3A%2F%2Fmydomain.com%2F&hl=de
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
// @grant        none
// ==/UserScript==
(function() {
    'use strict';
    // Simulate input
    let elem = document.querySelector("input[aria-label='Sitemap-URL eingeben']");
    elem.value = "sitemap.xml";
    elem.dispatchEvent(new Event('input', {
        bubbles: true,
        cancelable: true,
    }));
    // Click "Send" button
    setTimeout(() => {
        console.log("Clicking Send button");
        document.querySelector("c-wiz[data-node-index='2;0'] div[tabindex='0']").click();
    }, 500);
})();

 

Posted by Uli Köhler in Javascript

Javascript: How to send event after changing programmatically without JQuery

This snippet will change an input programmatically and then dispatch an input event which will tell the javascript code running on the page that the input has changed.

let myinput = document.querySelector("input#myinput");
// Set value
myinput.value = "newvalue";
// Simulate input event
myinput.dispatchEvent(new Event('input', {
    bubbles: true,
    cancelable: true,
}));

 

Posted by Uli Köhler in Javascript

How to set value of input in Tapermonkey script

This Tapermonkey script will find <input id="myinput"> and set its value to newvalue.

(function() {
    'use strict';
    document.querySelector("input#myinput").value = "newvalue";
})();

You’ll need to add a suitable header – it’s often best just to use the default template and modify only the URL.

Posted by Uli Köhler in Javascript

Best practice Angular 14 production build command

This is the command I use to make production builds of Angular 14 webapps:
ng build ---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

Angular HttpClient plaintext (text/plain) minimal example

In order for Angular’s HttpClient to process plaintext responses and not result in an error, you need to set responseType: 'text' in the options(which is the second parameter to .get(). Otherwise, Angular will try to parse the plaintext response, even if the response MIME type is set to text/plain.

getPlaintext(command: string): Observable<string> {
  return this.http.get(`/api/text`, { responseType: 'text' });
}

Full service example

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class PlaintextService {

  constructor(private http: HttpClient) { }
  
  getPlaintext(command: string): Observable<string> {
    return this.http.get(`/api/text`, { responseType: 'text' });
  }
}

 

Posted by Uli Köhler in Angular, Typescript

Angular HttpClient GET query parameter minimal example

sendCommand(command: string): Observable<any> {
  return this.http.get<any>(`/api/command`, {
    params: {"command": command}
  });
}

Running

sendCommand("test")

will send a HTTP GET request to /api/command?command=test.

Full service example

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class CommandService {

  constructor(private http: HttpClient) { }

  sendSerialCommand(command: string): Observable<any> {
    return this.http.get<any>(`/api/command`, {
      params: {"command": command}
    });
  }

}

 

Posted by Uli Köhler in Angular, Typescript

How to fix error NG8002: Can’t bind to ‘ngModel’ since it isn’t a known property of ‘input’.

Problem:

When trying to load your Angular app in ng serve you see an error message like

error NG8002: Can't bind to 'ngModel' since it isn't a known property of 'input'.

2     <input type="text" pInputText [(ngModel)]="text"/>
                                    ~~~~~~~~~~~~~~~~~~~~~

Solution:

You have not loaded FormsModule in your app.module.ts.

Import it using

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

and load it by appending

FormsModule,

to imports: [...] in app.module.ts , for example:

imports: [
  BrowserModule,
  AppRoutingModule,
  HttpClientModule,
  InputTextModule,
  FormsModule,
],
Posted by Uli Köhler in Angular, Javascript

EMQX 5.x HTTP ACL server using NodeJS

In our previous post EMQX 5.x HTTP password authentication server minimal example using NodeJS we provided a complete example of how to implement EMQX HTTP authentication.

This post provides an extension to our previous HTTP auth by adding a Koa router (i.e. a HTTP endpoint / URL) to provide ACL authentication, i.e. allow or deny topic level access with custom logic.

router.post('/emqx/acl', async ctx => {
    const body = ctx.request.body;
    console.log(body)
    // TODO: This example always returns true
    // You need to implement your authentication logic
    ctx.body = {
        result: "allow",
    };
});

Add that code before app.use(router.routes()); in the script from EMQX 5.x HTTP password authentication server minimal example using NodeJS.

My recommended Authorization configuration body which you can set in the EMQX dashboard is

{
  "action": "${action}",
  "client_id": "${clientid}",
  "ip": "${peerhost}",
  "topic": "${topic}",
  "username": "${username}"
}

 

Posted by Uli Köhler in EMQX, MQTT, NodeJS

EMQX 5.x HTTP password authentication server using NodeJS

Note: If you also want to implement ACL authorization via HTTP, see our extension to this example: EMQX 5.x HTTP ACL server using NodeJS

This server implements a minimal HTTP authentication server. In this minimal example, it will always allow authentication – you need to implement your own logic to verify passwords. Note: This server is written for EMQX versionx5.0 and will not work for EMQX 4.x without modification. See the official documentation on EMQX 5.x HTTP auth for more information.

#!/usr/bin/env node
const router = require('koa-router')();
const koaBody = require('koa-body');
const Koa = require('koa');
const app = new Koa();

app.use(koaBody());

router.post('/emqx/auth', async ctx => {
    const body = ctx.request.body;
    const username = body.username;
    const password = body.password;
    // TODO: This example always returns "allow"
    // You need to implement your authentication logic
    ctx.body = {
        result: "allow",
        is_superuser: false
    };
});

app.use(router.routes());

if (!module.parent) app.listen(19261);

This script is based on our previous post Minimal Koa.JS example with Router & Body parser, hence you can install the dependencies using

npm i --save koa koa-router koa-body

The request body config which you can set in the EMQX dashboard is

{
  "client_id": "${clientid}",
  "password": "${password}",
  "username": "${username}"
}

 

Posted by Uli Köhler in EMQX, MQTT, NodeJS

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:

[env:esp32dev]
platform = espressif32
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.3
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:

#!/bin/sh
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