Let's explore the utilities under @angular/cdk/coercion namespace
Angular is a feature-rich framework that has been globally adopted for small-scale to enterprise-scale web applications. One of the most obvious reasons is the tremendous amount of APIs and utilities available out of the box to help in everyday development. The core team at Angular has done an amazing job in maintaining & thoroughly documenting everything along with the community contributors, but obviously, there are some patches that are contributed by community and still pending to be documented. In this article, we will explore one of those interesting and quite helpful set of utilities, i.e. Coercion.
What is Coercion? Coercion is the process of implicitly converting data to a required format.
There are no hard and fast rules for coercion or implicit type conversion, and may vary from case to case. But our friends at Angularand the amazing communitygot us covered for some basic data types like arrays, booleans, numbers, etc. They’ve built a collection of APIs that can be found under @angular/cdk/coercion namespace.
We'll take a detailed look at these utilities in the following sections. In all the examples, we’ll pass data as @Input properties of our example components and try to coerce the passed values through the coercion APIs, but these APIs can be used anywhere based on your needs.
Angular Ivy comes with stricter type checking that can be optionally disabled by using the TypeScript config (tsconfig.json) flags. We’ll see code examples that work with strict type checking enabled as well. You can read more about Angular’s Template type checkinghere.
export function coerceArray(value: T | T): T
Wraps the provided value in an array, unless it is an array already
Consider an example of a component that takes an array of strings and an array of a custom type Person as @Input.
This is how typically it will be used:
It will be nice if we allow passing in a single value and make it work without errors, like this:
To get this working, we will have to make the following changes to the ArrayCoercionComponent.
Q: What happens if the passed in value is null or undefined?
A: It gets wrapped into an array as well, see console log below:
You can see, we still get an array but with a single item being null or undefined. I believe this API can be improved in the following ways:
If the value is null or undefined, it should not be wrapped in an array, so the consumers can do a simple truthy/falsy check.
Remove all null or undefined items from the array, so to make it clean to be bound without additional truthy/falsy checks on array items.
As we have seen in the above examples, using the coerceArray method, we allow the users of our component to pass in an array or a single value without producing any error.
function coerceBooleanProperty(value: any): boolean
Converts a value (typically a string) to a boolean
Let’s take an example of a component that takes two boolean flags as @Input.
Example usage of this component will be like the following examples:
Although these examples show the common way of usage, but as these are booleans, it will be great if we can allow using them like these as well:
Let’s make some changes to our BooleanCoercionComponent to make it work with the above usage examples as well.
Note: In the strict type checking scenario, we are allowing any value to be passed, the catch here is that if an object is passed as input, it will also be coerced to a boolean. That’s where the component users will have to be careful while passing the inputs to avoid unwanted results.
The coerceBooleanProperty utility method is super handy to allow your component users with multiple ways to pass boolean inputs.
function coerceCssPixelValue(value: any): string
Coerces a value to a CSS pixel value
This might be rare, but if you are in a situation where you have a need to pass a CSS pixel value to a component, which might use that value for some inline styling or some other logic, you will have something like this:
The example usage of the above component will be the following:
It will be more intuitive if we allow the users of our component to pass in number, pixel value, em, or a value with any other valid unit. Examples of such usage can be like this:
To make this work, let’s see how our CssPixelCoercionComponent will look like.
[A]: As we saw that the coerceCssPixelValue method takes the value parameter of type any which means we could’ve written the setter as:
set paddingY(val: any)
But we restricted it only to pass in number or string only. The reason was that, if the user passes null or undefined, it would be converted to empty strings and if an object gets passed, it would be stringified as [object Object]px. So, we restricted the input but kept the flexibility to pass valid, usable values.
function coerceElement(elementOrRef: ElementRef | T): T
Coerces an ElementRef into an Element, unless it is already an Element
At times, we might need a component to accept a native HTML element as an input of our component and add some functionality around it. An example component might look something like this:
Working with Angular, we make use of ElementRef wrapper very frequently, and we might want to allow passing in ElementRef to our component. So we can change the input as:
Now, we can pass in native HTMLElement or ElementRef, as in the following example.
So far so good! But, in our ElementCoercionComponent, we needed only native HTML element. This is where we can make use of the coerceElement utility function, and change our component to look like this:
function coerceNumberProperty(value: any, fallbackValue = 0): number
Coerces a data-bound value (typically a string) to a number
Passing numbers as component inputs or function arguments is another very common practice. Let’s suppose we have a component that takes two numbers as input.
Example usage of this component can be something like this:
It would be great if we can allow the following usage as well:
The input value can also be null or undefined and could cause unwanted situations or runtime errors. Let’s make some changes to our NumberCoercionComponent to make sure that the input values are always valid numbers after we receive them.
There you go!
In the sections above, we saw how these small utilities help us make sure that our component inputs receive the data gracefully with some level of flexibility.
Although this is a small list of utilities, I am sure we can make improvements to the existing ones and add more to this list for the amazing community. I would recommend you to take a look at the implementation of these APIs here.
Looking forward to hearing your thoughts and feedback. Happy coding!