DEV Community

Cover image for Various ways to make an Input mandatory in Angular
Pierre Bouillon for This is Angular

Posted on • Updated on

Various ways to make an Input mandatory in Angular

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

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:

@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';
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

@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');
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

NgOnInit error

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:

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

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

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:

feat(compiler): add support for compile-time required inputs #49468

This is a re-submit of #49453.

Adds support for marking a directive input as required. During template type checking, the compiler will verify that all required inputs have been specified and will raise a diagnostic if one or more are missing. Some specifics:

  • Inputs are marked as required by passing an object literal with a required: true property to the Input decorator or into the inputs array.
  • Required inputs imply that the directive can't work without them. This is why there's a new check that enforces that all required inputs of a host directive are exposed on the host.
  • Required input diagnostics are reported through the OutOfBandDiagnosticRecorder, rather than generating a new structure in the TCB, because it allows us to provide a better error message.
  • Currently required inputs are only supported during AOT compilation, because knowing which bindings are present during JIT can be tricky and may lead to increased bundle sizes.

Fixes #37706.

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

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

If not provided, Angular will throw a compilation error:

With required compilation error

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 where you can find all the components and their usage.


I hope that you learnt something useful there!


Photo by Sigmund on Unsplash

Top comments (3)

Collapse
 
ezzabuzaid profile image
ezzabuzaid

Good writeup!

Collapse
 
spock123 profile image
Lars Rye Jeppesen

Ng 16 looks to be a very potent release.
Signals are very very cool

Collapse
 
danielepecora profile image
Daniele Pecora

Yes ,😀 but let's just don't stop there ...
I would love to see Angular to fully adapting JSON Schema validation.
And with adapting I mean fully builtin 🤩