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:

{
    "/api":
    {
        "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 --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 => {
          console.info(params.id);
      })
}

 

Posted by Uli Köhler in Angular, Javascript

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 ng: not found

Problem:

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

Note: This command does not work for Angular 14+ and is only kept for reference. See Best practice Angular 14 production build command for an updated 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.

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

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

Problem:

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"?

Solution:

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

Symptom:

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
[...]

Solution:

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:

@NgModule({
  imports: [
    RecallCommonModule,
    CommonModule,
    MyRoutingModule
  ],
  declarations: [,
    MyDetailComponent,
    MySearchComponent
  ],
  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

Problem:

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)
[...]

Solution:

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/MyComponent.html@5: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’

Problem:

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>
<div #elseSection>
    <!-- ... -->
</div>

Solution:

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">
    <!-- ... -->
</div>
<ng-template #elseSection>
    <!-- ... -->
</ng-template>
Posted by Uli Köhler in Angular, Javascript

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

Problem:

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

Cannot find control with unspecified name attribute

Solution:

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

[formControl]="myCtrl"

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

[formControl]="myControl"

fixed the issue

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

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

Problem:

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

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

Solution:

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