Various ways to make an Input mandatory in Angular
Tags: angular, tutorial, javascript, webdev
Published: 2023-04-03 04:50:47

When creating a new component, we often build it around specific inputs and, in some cases, that component has no reason to exist without those inputs.

Fortunately, Angular offers several ways to ensure that an input has been provided, let's review some of them!

## Table of Contents

- [Using `NgIf`](#using-ngif)
- [Using lifecycle hooks](#using-lifecycle-hooks)
- [Using the selector](#using-the-selector)
- [Using the `required` option](#using-the-required-option)

## Using `NgIf`

One of the most common ways to display the template of a component only when an input has a value is to check it in the `NgIf` directive:

```ts
@Component({
  selector: 'app-with-required',
  standalone: true,
  imports: [NgIf],
  template: '<div *ngIf="isDefined">Value is: {{ value }}</div>'
})
export class WithNgIfComponent {
  @Input() value!: number;

  get isDefined(): boolean {
    return typeof this.value === 'number';
  }
}
```

Using this approach, the template will not be displayed if the `value` has not been provided.

This somewhat makes the `@Input` mandatory for the view to be shown but it might not be optimal for us.

Firstly, not passing a value will not throw any kind of error. If someone else would like to use our component, having our component explicitly tell what it needs for it to work would be better than just guessing what is required and what is not.

Secondly, since passing a value is not enforced, `value` can be `undefined` and any logic we might have in our component would require us to write defensive code anywhere we are using this value.

## Using lifecycle hooks

To tackle the first issue, we can update our component to implement the `OnInit` lifecycle hook and programmatically check if the value has been provided:

```ts
@Component({
  selector: 'app-with-ngoninit',
  standalone: true,
  template: '<div>Value is: {{ value }}</div>'
})
export class WithNgOnInitComponent implements OnInit {
  @Input() value!: number;

  ngOnInit(): void {
    if (this.value === undefined) {
      throw new Error('`value` is required');
    }
  }
}
```

This is a bit better since we can now see an error in the console if we do not provide the value:

![NgOnInit error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w0kxbv7w3fgq8rw69lez.png)

That's some progress! However, this still is not optimal: the error, even if there is one this time, it is only thrown at runtime. This makes our component a bit harder to work with since its incorrect behavior is not visible until invoked.

## Using the selector

It would certainly be better if we could see that error earlier.

One way to achieve this is to enforce this is to take advantage of the Angular Language Service and to directly add the input in the selector of the component:

```ts
@Component({
  selector: 'app-with-selector[value]',
  standalone: true,
  template: '<div>Value is: {{ value }}</div>'
})
export class WithSelectorComponent {
  @Input() value!: number;
}
```

By doing so, using the component without passing the input would result in Angular not knowing what component we are trying to use:

![With selector error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdrtinnx1nw2j01r8sq1.png)

Similarly, passing the input to the component will work just fine.

However, with this approach, the error message is not very explicit since it is telling us about an unknown component instead of the missing input.

> It is also important to note that Angular is not aware that the input in your selector is the same as the `@Input` you defined. It means that if you were to rename it, you would have to be sure to manually make the change in the selector too

## Using the `required` option

All solutions that were presented are about tradeoffs:

- Using `NgIf` we are silencing the error but saving ourselves the code to check the input
- Using `OnInit` we can ensure that the value has been provided but also have to write extra code for that
- Using the selector, we can enforce the usage of the @Input and get an error message at compile time. However, the error message is not very specific, as it tells us about an unknown component instead of the missing input

Fortunately, the Angular dev team has noticed this issue as well and is addressing it in the upcoming version 16 of the framework:

{% embed https://github.com/angular/angular/pull/49468 %}

The suggestion is to provide an object literal to the `@Input` to indicate whether this input is mandatory or not:

```ts
@Component({
  selector: 'app-with-required',
  standalone: true,
  template: '<div>Value is: {{ value }}</div>'
})
export class WithRequiredComponent {
  @Input({ required: true }) value!: number;
}
```

If not provided, Angular will throw a compilation error:

![With required compilation error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cwc46piomqrqyil6cude.png)

You can notice that this time the error message is both during the compilation and very explicit as it tells us what property is missing, on which component and on which line.

An advantage of using the object literal to enhance the current `@Input` behavior is that it is backward compatible: previous solutions will still work without it and, once the migration is done to Angular 16, you can take advantage of it too in your code base with little to no breaking changes.

---

In this article we saw four different ways of ensuring that an input has been provided to our component:

- Checking in the template using `NgIf`
- Checking during the component initialization using `OnInit`
- Taking advantage of the selector syntax to bring the Angular Language Service to the rescue
- Using the new `required` flag of Angular 16 in the `@Input` options

I personally prefer the `required` flag since I think this is the most explicit and straightforward way to conveys the agreement I aim to establish between the component and potential consumers.

> If you would like to check the resulting code, you can head on to the associated [GitHub repository](https://github.com/pBouillon/DEV.MandatoryInputs) where you can find all the components and their usage.

---

I hope that you learnt something useful there!

{% embed https://dev.to/pbouillon %}

---

> Photo by <a href="https://unsplash.com/@sigmund?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Sigmund</a> on <a href="https://unsplash.com/photos/OKeu92CvolE?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>