Typescript

How to fix Angular standalone component: Can’t bind to ‘routerLink’ since it isn’t a known property of ‘a’

Problem:

When opening your Angular app with a standalone component, you see the following error message:

Can't bind to 'routerLink' since it isn't a known property of 'a'

Solution:

In the component where the error occurs, add

import { RouterModule } from '@angular/router';

and add

RouterModule,

to the imports of the component. Example:

@Component({
  selector: 'app-top-bar',
  standalone: true,
  imports: [
    CommonModule,
    RouterModule
  ],
  templateUrl: './top-bar.component.html',
  styleUrl: './top-bar.component.sass'
})
export class TopBarComponent {

}

 

Posted by Uli Köhler in Angular, Typescript

How to make header row bold using xlsx / SheetJS

Based on our previous example How to make cell bold using xlsx / SheetJS, the following code example will set {s: {font: {bold: true}} for every cell of the first row.

This code assumes that you know the number of columns (header.length in this example).

Add the following code after adding the cell content to the worksheet.

// Make first row bold
for(let i = 0; i < headers.length; i++) {
    const cell = ws[XLSX.utils.encode_cell({r: 0, c: i})];
    // Create new style if cell doesnt have a style yet
    if(!cell.s) {cell.s = {};}
    if(!cell.s.font) {cell.s.font = {};}
    // Set bold
    cell.s.font.bold = true;
}

 

Posted by Uli Köhler in Javascript, Typescript

How to make cell bold using xlsx / SheetJS

First, you need to understand that stock xlsx / SheetJS community edition does not support cell styling as of August 2023.

Hence, you need to use the fork xlsx-js-style which you can install using

npm i --save xlsx-js-style

Now, based on our previous example, use these imports

import * as XLSX from 'xlsx-js-style';
import { saveAs } from 'file-saver';

and this code to generate a XLSX file with a bold cell:

// Create an empty workbook
const wb = XLSX.utils.book_new();

// Create an empty worksheet
// If this weren't a minimal example, your data would go here
const ws = XLSX.utils.aoa_to_sheet([
    ["Test"]
]);

// Make cell bold
ws["A1"].s = {
    font: {
        bold: true,
    }
};

// Add the worksheet to the workbook
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');

// Export the workbook to XLSX format
const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'buffer' });

// Convert the binary data to a Blob
const blob = new Blob([wbout], { type: 'application/vnd.ms-excel' });

// Download of the file using file-saver
saveAs(blob, 'example.xlsx');

 

Posted by Uli Köhler in Javascript, Typescript

How to export & download XLSX in the browser in Angular (minimal example)

This example uses the xlsx library and the file-saver library to create an (empty) XLSX file and download it, all on the client side.

First, install the libraries:

npm i --save file-saver xlsx
npm i --save-dev @types/file-saver

Import them using

import * as XLSX from 'xlsx';
import { saveAs } from 'file-saver';

Here’s the main part of the code that creates the XLSX:

// Create an empty workbook
const wb = XLSX.utils.book_new();

// Create an empty worksheet
// If this weren't a minimal example, your data would go here
const ws = XLSX.utils.aoa_to_sheet([[]]);

// Add the worksheet to the workbook
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');

// Export the workbook to XLSX format
const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'buffer' });

// Convert the binary data to a Blob
const blob = new Blob([wbout], { type: 'application/vnd.ms-excel' });

// Download of the file using file-saver
saveAs(blob, 'example.xlsx');

 

Posted by Uli Köhler in Typescript

How to import saveAs() from file-saver in Typescript (Angular)

First, install the library for your angular or other typescript project:

npm i --save file-saver
npm i --save-dev @types/file-saver

Now you can import it using

import { saveAs } from 'file-saver';

 

Posted by Uli Köhler in Typescript

Which tsc –target to use for different NodeJS versions?

Depending on the minimum NodeJS version you intend to support,, you should use the following –target settings for tsc:

  • Node 10.x: es2018
  • Node 12.x: es2019
  • Node 14.x: es2020
  • Node 16.x: es2021
  • Node 17.x: es2022
  • Node 18.x: es2022
  • Node 19.x: es2022
  • Node 20.x: es2022

Note that there are more configurations possible than just --target when compiling for NodeJS. See the tsconfig base config examples on Github for more details.

Source: The tsconfig bases on Github.

Posted by Uli Köhler in NodeJS, Typescript

How to fix Angular error TS2552: Cannot find name ‘Input’. Did you mean ‘oninput’?

Problem:

You see the following error message while compiling your angular application:

Error: src/app/my/my.component.ts:9:4 - error TS2552: Cannot find name 'Input'. Did you mean 'oninput'?

Solution:

Open your module file, e.g. my.component.ts and add the following line at the top:

import { Input } from '@angular/core';

 

Posted by Uli Köhler in Angular, Typescript

How to get filesize in NodeJS / TypeScript using Promisese

First, import stat from the NodeJS standard library:

import { stat } from "node:fs/promises";

Async-await style

// Async-await style:
const statResult = await stat("myfile.pdf");
const fileSizeInBytes = statResult.size;

Promise.then() style

stat("myfile.pdf").then(statResult => {
    const fileSizeInBytes = statResult.size;
    // TODO your code goes here
});

 

Posted by Uli Köhler in NodeJS, Typescript

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

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

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 Angular Service ngOnInit() not being called

Problem:

You have an Angular service implementing OnInit:

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

@Injectable()
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.

Solution:

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';

@Injectable()
export class MyService {

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

 

Posted by Uli Köhler in Angular, Typescript

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

Problem:

You have angular HTTP client code like

this.http.get<MyType>(`${this.baseURL}/api/myAPI`).toPromise()

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

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

Solution:

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
this.http.get<MyType>(`${this.baseURL}/api/myAPI`).toPromise()
// After
this.http.get<MyType>(`${this.baseURL}/api/myAPI`)

and surround the entire statement with firstValueFrom:

// Before
this.http.get<MyType>(`${this.baseURL}/api/myAPI`)
// After
firstValueFrom(this.http.get<MyType>(`${this.baseURL}/api/myAPI`).toPromise())

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';

@Injectable()
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 => 
      this.values.next(value);
    });
  }
}

 

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

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

Problem:

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;
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  ../node_modules/@angular/core/core.d.ts:7888:47
    7888     (selector: Type<any> | Function | string, opts: {
                                                       ~~~~~~~
    7889         read?: any;
         ~~~~~~~~~~~~~~~~~~~
    7890         static: boolean;
         ~~~~~~~~~~~~~~~~~~~~~~~~
    7891     }): any;
         ~~~~~
    An argument for 'opts' was not provided.

Solution:

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