Or at least you should be using boolean @Inputs in Angular less often. Because when they are introduced in Angular components it often leads to problems. It is in most cases not extendable and causes unnecessary dependencies. In this article, I am presenting four examples where boolean Inputs are a problem, and I am showing a better alternative.
#1 Boolean inputs are often unnecessary
In this first problem case, an Angular boolean input is introduced into a component unnecessarily. The logic that depends on the boolean input could also be dependent on other data. Let’s see a simple example. In a ProductComponent
there is a boolean @Input showProductDescription
. As the name suggests, the variable is used to decide whether a description should be shown for a product.
export class ProductComponent {
@Input()
product: Product;
@Input()
showProductDescription: boolean;
}
The value for this boolean input is calculated on the consumer side of the ProductComponent
. Probably it looks like the following and is solely dependent on the product itself: !!product.description
. So, it just checks on the presence of the description value on the product.
The component’s template consumes then the flag in a ngIf:
<article>
<img [src]="'/assets/images/shop/' + product.image" />
{{ product.name }} - {{ product.price | price }}
<p *ngIf="showProductDescription">
{{ product.description }}
</p>
</article>
The above implementation comes with the following problems:
- The
showProductDescription
is an additional input to theProductComponent
and makes its interface more complex. - If this
showProductDescription
is also needed further down the component hierarchy it needs to be passed multiple times alongside the product. This makes code hard to change.
The alternative
We can just make the displaying of the paragraph dependent on the description
field of the product. Then we can get rid of showProductDescription
. The ngIf is therefore updated and receives the argument product.description
.
<article>
<img [src]="'/assets/images/shop/' + product.image" />
{{ product.name }} - {{ product.price | price }}
<p *ngIf="product.description">
{{ product.description }}
</p>
</article>
This first example was an easy one. I guess most Angular developers do not make such mistakes. However, I have seen such cases already and wanted to point them out too. The next case will be more interesting.
#2 Boolean inputs are hard to extend
As we know a boolean can only have two values (or four if you count null/undefined). When we want to extend by a third variant, we are restricted because of the type. Let’s see an example. For that, we stay with the theme of products. But this time we deal with product availability:
export class ProductAvailabilityComponent {
@Input() availability: boolean;
}
We have a ProductAvailabilityComponent
that shows if the product is available or not. The corresponding template might look like that:
<span *ngIf="availability; else notAvailable">Product is available</span>
<ng-template #notAvailable>
<span>Unfortunately not available</span>
</ng-template>
And availability is always a boolean, isn’t it? Well, it turns out that our products can be unavailable in the online shop, but could be available for preorder. And we want to inform the customers about that fact. So, now we have three mutually exclusive options: available, available for preorder, and unavailable. But availability is not easily extendable because it’s a boolean. Therefore we add another flag:
export class ProductAvailabilityComponent {
@Input() availability: boolean;
@Input() preorderPossible: boolean;
}
Consequently, our template becomes something like this:
<span *ngIf="availability; else notAvailable">Product is available</span>
<ng-template #notAvailable>
<span *ngIf="preorderPossible; else reallyNotAvailable">
Not available yet. Preorder possible.
</span>
<ng-template #reallyNotAvailable>
<span>Unfortunately not available</span>
</ng-template>
</ng-template>
This doesn’t look too good. The template is already a bit bloated and the ngIf-else logic is not ideal. Especially if we would like to add a fourth possible value in the future it’s not clear at first sight how to do it. But there is for sure a cleaner way.
The alternative
We need to define the availability as an extendable data type. In this case, we choose a union type. Availability changes from boolean to:
export type Availability = 'AVAILABLE' | 'NOT_AVAILABLE' | 'PREORDER_POSSIBLE';
And our component and template can be simplified to the following:
export class ProductAvailabilityComponent {
@Input() availability: Availability;
}
<div [ngSwitch]="availability">
<span *ngSwitchCase="'AVAILABLE'">Product available</span>
<span *ngSwitchCase="'PREORDER_POSSIBLE'">Not available yet. Preorder possible.</span>
<span *ngSwitchCase="'NOT_AVAILABLE'">Unfortunately not available</span>
</div>
This is more extendable and we can even add a fourth value without a problem.
#3 Boolean inputs cause breaking changes
This is related to point #2 above. The consumer of the ProductAvailabilityComponent
relied on a boolean and now would have to pass the two booleans availability
and preorderPossible
. This is a breaking change in most situations. Would the data type have been the Availability union type from the beginning then the consumer can adapt more easily.
This becomes even clearer when the ProductAvailabilityComponent
is deep down a component tree. The type potentially has to be adapted in the whole chain from Store, StoreFacade, ProductPageComponent, ProductComponent, ProductDetailComponent to ProductAvailabilityComponent
. Depending on the setup and where the product availability is determined this could result in changes in a lot of files.
The alternative is, as mentioned, to use a type that allows adding further values. Also moving from the boolean to the union type is quite some change. You should go with the more extendable type from the beginning if you can’t rule out an extension to the values.
#4 Boolean inputs bloat your templates
Here we are considering again the example from point #2 where we had the two boolean flags and ended up with the following template:
<span *ngIf="availability; else notAvailable">Product is available</span>
<ng-template #notAvailable>
<span *ngIf="preorderPossible; else reallyNotAvailable">
Not available yet. Preorder possible.
</span>
<ng-template #reallyNotAvailable>
<span>Unfortunately not available</span>
</ng-template>
</ng-template>
This looks terrible. It is hard to read and hard to extend. Especially if all options are mutually exclusive there is a cleaner way where everything is on one level.
The alternative
We have already seen a better way and it looked like that using a ngSwitch:
<div [ngSwitch]="availability">
<span *ngSwitchCase="'AVAILABLE'">Product available</span>
<span *ngSwitchCase="'PREORDER_POSSIBLE'">Not available yet. Preorder possible.</span>
<span *ngSwitchCase="'NOT_AVAILABLE'">Unfortunately not available</span>
</div>
All the cases are handled the same way which brings clarity and consistency. But we can even simplify more in certain situations when it is just about showing the text:
<span>{{ ('PRODUCT_AVAILABILITY.' + availability ) | translate }}</span>
The translation keys can be directly constructed based on the availability union type value. This leaves us with a one-liner in the template compared to about 9 lines before.
Conclusion
Importantly, you should not stop entirely using boolean inputs in Angular. But you should think carefully about the above scenarios when introducing one. In a lot of cases, your code becomes simpler and better maintainable when you choose alternative types.
If the boolean input really maps to something that is not extendable and completely boolean it might be okay.
Another interesting article on boolean parameters in general, is What is wrong with boolean parameters? They suggest splitting methods that have a boolean parameter. Related to that, you could also think of splitting into two different components for certain cases where you started with a boolean input for a single component.