Why I use decorators for coersion in Angular

Denis Loh
4 min readMar 18, 2022

--

Photo by Manja Vitolic on Unsplash

When you write components you will typically come across the requirement of providing input fields, which shall be used as an attribute flag in your template code.

So, let’s create a small example:

@Component({
selector: 'my-component',
template: `
<div>{{myValue | json}}</div>
`
})
export class MyComponent {
@Input() myValue: boolean
}
@Component({
template: `
<my-component [myValue]="true"></my-component>
`
})
export class AppComponent {
}

Well, this will predictably and not surprisingly print true . But now we want to use something like this:

@Component({
template: `
<my-component myValue></my-component>
`
})
export class AppComponent {
}
Photo by Jeff Kingma on Unsplash

This is what you will get then:

Error in src/app/app.component.html (1:15)Type 'string' is not assignable to type 'boolean'.

So, what’s wrong with that? When you just use the input field as an attribute without property binding, it will be treated as a string literal¹. That however means, our property myValue will receive an empty string '' .

Coerse to a boolean value

To solve this issue, we have to accept and handle also string values in our MyComponent . Let’s just enhance our component a bit:

@Component({
selector: 'my-component',
template: `<div>{{ myValue | json }}</div>`
})
export class MyComponent {
private _myValue: boolean = false;
@Input()
get myValue(): any { return this._myValue }
set myValue(value: any) { this._myValue = !!value }
}

But nah! It still doesn’t work:

false

While using !! works most of the times to coerse any value into boolean, it doesn’t work here as we expect an empty string "" to be true . So we have to adjust it again a bit:

set myValue(value: any) { 
this._myValue = value != null && `${value}` !== 'false'
}

Oh yeah! It works:

true

Luckily, you do not have to care about this coerse function that much, because there is already a helper function in the Angular CDK, which is called coerceBooleanProperty²:

...
import {
coerceBooleanProperty,
BooleanInput
} from '@angular/cdk/coercion';
@Component({
selector: 'my-component',
template: `<div>{{ myValue | json }}</div>`
})
export class MyComponent {
private _myValue: boolean = false;
@Input()
get myValue(): BooleanInput { return this._myValue }
set myValue(value: BooleanInput) {
this._myValue = coerceBooleanProperty(value)
}
}

But hey, this is quite a lot of boiler plate for a simple attribute, right? Can’t we do it more readable and shorter? Yes, we can!

Use decorators

With decorators³ we are able to generate the required helper property and reduce it to a single line of code. Let’s see how it’s done.

First we have to create the property decorator factory:

export function createPropertyDecorator<T>(
fn: (value: any, ...props:any) => T, ...props:any
) : PropertyDecorator {
return (target: Object, propertyKey: string | symbol) => {
const tmpKey = `${String(propertyKey)}Tmp`
function getter(this: any) : T {
return this[tmpKey]
}
function setter(this: any, value: any) {
this[tmpKey] = fn(value, props)
}
Object.defineProperty(target, tmpKey, {
value: undefined,
writable: true,
enumerable: true,
configurable: true
})
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
})
}
}

This factory creates a new decorator, which overrides our property with a customised getter and setter. It also constructs a storage property, where the result is held. Whenever we set our property, it will call the setter of our decorator and internally applies our converter function.

And now we write the actual decorator:

export function coerceToBoolean(value: any): boolean {
return value != null && `${value}` !== 'false'
}
export function ToBoolean() : PropertyDecorator {
return createPropertyDecorator<boolean>(coerceToBoolean)
}

Finally, we adjust our component again:

@Component({
selector: 'my-component',
template: `<div>{{ myValue | json }}</div>`
})
export class MyComponent {
@ToBoolean() @Input() myValue: boolean | string
}

We have to use boolean | string here to stay type-safe, but all values will be boolean.

You can find the working example on StackBlitz here: https://angular-ivy-btvkrv.stackblitz.io

Photo by Alexas_Fotos on Unsplash

One last remark…

While the Angular CDK function coerceBooleanProperty does it’s job, it has one particular flaw:

coerceBooleanProperty("0"); // returns true!

So, to solve this issue, we have to adjust our own coerce function as such:

export function coerceToBoolean(value: any): boolean {
return value != null && `${value}` !== 'false' && value != '0';
}

That’s it!

I hope this helps someone.

--

--

Denis Loh

I’m a passionate mobile solutions software architect at Deutsche Telekom MMS and helpdesk hotline for my parents in my spare time.