129

In AngularJs we could make a directive attribute required. How do we do that in Angular with @Input? The docs don't mention it.

Eg.

@Component({
  selector: 'my-dir',
  template: '<div></div>'
})
export class MyComponent {
  @Input() a: number; // Make this a required attribute. Throw an exception if it doesn't exist.
  @Input() b: number;
}
developer033
  • 24,267
  • 8
  • 82
  • 108
Simon Trewhella
  • 2,124
  • 3
  • 23
  • 23
  • 2
    In May 2023, Angular version 16, They added `required` like `@Input({ required: true }) a: number`. See Ruslan's answer below https://stackoverflow.com/a/76187833/1730846 – Mojtaba May 10 '23 at 15:39

10 Answers10

207

For Angular 16 and newer

The @Input() directive now directly supports marking something as required:

@Input({ required: true }) myRequiredInput!: unknown;

This change works on both Components and Directives.

Official solution (Angular 15 and below)

As answered by Ryan Miglavs – smart usage of Angular's selectors solves the issue.

/** Note: requires the [a] attribute to be passed */
@Component({
  selector: 'my-dir[a]', // <-- use attribute selector along with tag to ensure both tag name and attribute are used to "select" element by Angular in DOM
});
export class MyComponent {
  @Input() a: number;
}

Personally I prefer this solution in most cases, as it doesn't require any additional effort during the coding time. However, it has some disadvantages:

  • it's not possible to understand what argument is missing from the error thrown
  • error is confusing itself as it says, that tag isn't recognized by Angular, when just some argument is missing

Both of these negatives can be partially ameliorated by adding a decorative comment above the @Component decorator as seen above, and most editors will show that along with any tooltip information for the component name. It does not help with the Angular error output though.


For alternative solutions – look below, they require some additional codding, but doesn't have disadvantages described above.


So, here is my solution with getters/setters. IMHO, this is quite elegant solution as everything is done in one place and this solution doesn't require OnInit dependency.

Solution #2

Component({
  selector: 'my-dir',
  template: '<div></div>',
});
export class MyComponent {
  @Input()
  get a() {
    throw new Error('Attribute "a" is required');
  }
  set a(value: number) {
    Object.defineProperty(this, 'a', {
      value,
      writable: true,
      configurable: true,
    });
  }
}

Solution #3:

It could be done even easier with decorators. So, you define in your app once decorator like this one:

function Required(target: object, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    get() {
      throw new Error(`Attribute ${propertyKey} is required`);
    },
    set(value) {
      Object.defineProperty(target, propertyKey, {
        value,
        writable: true,
        configurable: true,
      });
    },
    configurable: true
  });
}

And later in your class you just need to mark your property as required like this:

Component({
  selector: 'my-dir',
  template: '<div></div>',
});
export class MyComponent {
  @Input() @Required a: number;
}

Explanation:

If attribute a is defined - setter of property a will override itself and value passed to attribute will be used. Otherwise - after component init - first time you want to use property a in your class or template - error will be thrown.

Note: getters/setters works well within Angular's components/services, etc and it's safe to use them like this. But be careful while using this approach with pure classes outside Angular. The problem is how typescript transpiles getters/setters to ES5 - they are assigned to prototype property of the class. In this case we do mutate prototype property which will be the same for all instances of class. Means we can get something like this:

const instance1 = new ClassStub();
instance1.property = 'some value';
const instance2 = new ClassStub();
console.log(instance2.property); // 'some value'
Mikkel Christensen
  • 2,502
  • 1
  • 13
  • 22
Ihor
  • 3,219
  • 2
  • 18
  • 20
  • 24
    nice use of the decorator -- scales well – gavs Jun 23 '18 at 00:58
  • 3
    What if I pass null/undefined to the property? – mdarefull Mar 13 '19 at 15:48
  • The use of a decorator is far superior to trying to leverage Angular's component selector syntax. This should be the accepted answer in my opinion. – crush Mar 17 '20 at 16:22
  • The only drawback is that you cannot use the property in `constructor` as it will raise the error. You have to implement `OnInit` – Miquel Apr 01 '20 at 22:01
  • I am getting an error using this decorator: `Cannot redefine property: show` with `@Input() @Required show: Subject`. It obviously cannot use the Object.defineProperty on the field – Tomas Lukac Apr 11 '20 at 12:06
  • Yes I have the same as @TomasLukac. Where do we need to define the decorator in order to work for the entire application? – Matthijs Jun 26 '20 at 12:55
  • 2
    Same Here. "configurable: true" seems to not working anymore – Gollm Jul 14 '20 at 09:10
  • 2
    You need to add the `configurable: true` flag to the parent `Object.defineProperty` call in the `Required` decorator. Otherwise, it throws a "can't redefine" error. Looks like the author left it out – Richard Francis Sep 30 '20 at 14:54
  • 1
    Supplemental to this, to satisfy the compiler if you have strictPropertyInitialization set to true, it might be best to add a not null assertion to the type: @Input() a!: number; avoids the Property 'a' has no initializer and is not definitely assigned in the constructor complaint – tplusk Jan 14 '21 at 02:20
  • 2
    The decorator approach doesn't seem to work even with `configurable: true` – danwellman Mar 16 '21 at 10:44
  • 1
    You should note that the use of the attribute selector (ie. `my-dir[a]`) does not work when you use `CUSTOM_ELEMENTS_SCHEMA` in your `app.module.ts` (as it allows any HTML element to be used) – Typhon May 19 '21 at 13:53
  • 2
    Suggesting an edit to the official solution: add a `!` to make `@Input() a!: number;`. Otherwise, in Angular strict mode, you can run into trouble for not also setting the Input value in the constructor. – JellicleCat Jun 23 '21 at 15:01
  • 2
    An additional drawback of the decorator/getter-setter solution is that it doesn't check the existence of the property until the property is read. If a required input is only read during an edge case, the consuming developer may never encounter the exception, meaning the attribute didn't provide the assumed safety. It's only as good as a normal type error in that way. – Benjamin Leeds Sep 15 '21 at 17:51
  • 2021 here. The decorator approach doesn't work for me. I always get `Attribute x is required` – Ionel Lupu Sep 30 '21 at 10:37
  • Regarding your last part about getters/setters being transpiled weirdly into prototypes: when I add an actual property to your linked sandbox, I can't even reproduce the problem you mentioned in the code example below. – Panossa Mar 10 '23 at 13:06
79

Check in ngOnInit() (inputs aren't yet set when the constructor is executed) whether the attribute has a value.

Component({
    selector: 'my-dir',
    template: '<div></div>'
})
export class MyComponent implements OnInit, OnChanges {
    @Input() a:number; // Make this a required attribute. Throw an exception if it doesnt exist
    @Input() b:number;

    constructor(){
    }

    ngOnInit() {
       this.checkRequiredFields(this.a);
    }

    ngOnChanges(changes) {
       this.checkRequiredFields(this.a);
    }

    checkRequiredFields(input) {
       if(input === null) {
          throw new Error("Attribute 'a' is required");
       }
    }
}

You might also check in ngOnChanges(changes) {...} if the values wasn't set to null. See also https://angular.io/docs/ts/latest/api/core/OnChanges-interface.html

Marco
  • 1,073
  • 9
  • 22
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • You might also want to check for undefined and give a specific error message for that... if a value is passed by the attribute and it's misspelled or undefined for some other reason, this will call attention to that fact more quickly, which will make it easier to debug. – jpoveda Aug 18 '16 at 22:49
  • thanks, but there is no mechanism for that provided by the framework, correct? – Max Koretskyi Feb 07 '17 at 13:14
  • 1
    Better onChanges since it's called when changes happen as init comes laters – Jimmy Kane Apr 16 '18 at 18:30
  • 1
    Good point. It might depends on what you try to accomplish. `ngOnInit` is a good place if the initial value should be checked, `ngOnChanges` if every update should be checked as well. – Günter Zöchbauer Apr 16 '18 at 18:34
  • Actually, it seems that there IS framework support from now, see the answer from Ryan Miglavs below : https://stackoverflow.com/a/54240378/2294082 – Adrien Dos Reis Jan 30 '19 at 14:18
  • @AdrienDosReis that doesn't make the input required, it only prevents the element to be elevated to an Angular component if the binding is missing. I find that rather ugly. – Günter Zöchbauer Jan 30 '19 at 14:21
  • 1
    @GünterZöchbauer And what's the difference (honest question, not rhetorical) ? If I'm developing a custom Component and I don't want it to be used without a specific Input, why is this solution "rather ugly" for you ? IMO the compiler (or your IDE) tells you about the fact that the component is misused (even if I reckon that the message is not that clear), so that's even better than waiting for a runtime error... – Adrien Dos Reis Jan 31 '19 at 11:06
  • I would just throw an exception in `ngOnInit` that an input value is missing. That's way more explicit and helpful than wondering and searching what might be the reason why an HTML element does not become a component. The only better option would be to get a warning in the IDE without even running the code. – Günter Zöchbauer Jan 31 '19 at 11:09
  • I favour compile-time warnings over run-time errors. On top of that, this is verbose boilerplate that has to go in each and every component, as opposed to a generic solution (making an attribute required by using the selector, OR going the decorator way) – Michahell Dec 06 '21 at 23:44
  • 1
    @MichaelTrouw I fully agree . I just tried to offer a workaround with what is available. – Günter Zöchbauer Dec 07 '21 at 12:59
60

The official Angular way to do this is to include the required properties in the selector for your component. So, something like:

/** Note: requires the [a] attribute to be passed */
Component({
    selector: 'my-dir[a]', // <-- Check it
    template: '<div></div>'
})
export class MyComponent {
    @Input() a:number; // This property is required by virtue of the selector above
    @Input() b:number; // This property is still optional, but could be added to the selector to require it

    constructor(){

    }

    ngOnInit() {
        
    }
}

The advantage to this is that if a developer does not include the property (a) when referencing the component in their template, the code won't compile. This means compile-time safety instead of run-time safety, which is nice.

The bummer is that the error message the developer will receive is "my-dir is not a known element", which isn't super clear.

That negative can be partially ameliorated by adding a decorative comment above the @Component decorator as seen above, and most editors will show that along with any tooltip information for the component name. It does not help with the Angular error output though.

I tried the decorator approach mentioned by ihor, and I ran into issues since it applies to the Class (and therefore after TS compilation to the prototype), not to the instance; this meant that the decorator only runs once for all copies of a component, or at least I couldn't find a way to make it work for multiple instances.

Here are the docs for the selector option. Note that it actually allows very flexible CSS-style selector-ing (sweet word).

I found this recommendation on a Github feature request thread.

LocalPCGuy
  • 6,006
  • 2
  • 32
  • 28
Ryan Miglavs
  • 609
  • 5
  • 4
  • 6
    "The official Angular way to do this is to include the required properties in the selector for your component" Can you please post a reference to this? I couldn't find anything official from Angular that would state this. Thanks! – Alex Szabo Jun 04 '19 at 12:20
  • 1
    @AlexSzabó alxhub (from Angular core team) said that is the recommended way: https://github.com/angular/angular/issues/18156#issuecomment-316233637 – developer033 Feb 23 '20 at 00:03
  • 5
    @developer033 The problem with this approach is the error message is misleading. It leads you to believe that your component hasn't been registered with Angular via some module, when in fact, you just forgot to add a required attribute. I would also contest that this is the "official Angular way" to do this simply because a contributor mentioned it was *a* recommended way. Like I said, it results in an extremely misleading and hard to debug error being thrown. – crush Mar 17 '20 at 16:19
  • @crush Hey, I just answered **AlexSzabó's** question (after almost a year, it is true :) ). Of course, this error message could be improved as well as several others could be in Angular framework. About your **contestation**: I guess you didn't read well. First, I didn't said that it's the "official Angular way". Second, the user I mentioned isn't a simple contributor as you said, but an Angular core team member, as I already said in my comment above. Also, the solution from this answer is just another solution among many possible ones. People can choose which one best fits them. Regards :) – developer033 Mar 17 '20 at 17:53
  • 2
    @developer033 It literally says "official Angular way" in the answer. Just saying. I realize this isn't YOUR answer. Until Angular says this is how to do it on their style guide, I will disregard a one off comment on a long closed issue from a core team member. Cheers. – crush Mar 17 '20 at 19:58
  • Hi. I like the compile-timeness of the error, but, as said above, the error message is super-misleading. But most importantly, the mandatoriness of the input should be on the input field itself! To avoid repetition in another place (selector) and to be close to how programming language fields are specified as mandatory. – KarolDepka Apr 11 '20 at 04:31
  • 4
    Actually inputs should be mandatory by default, to mirror how it is in TypeScript and make it more fail-fast. – KarolDepka Apr 11 '20 at 04:32
  • 1
    I came to this question because I just turned on the `strictPropertyInitialization` flag in my tsconfig and noticed that a whole bunch of my components don't mark the input as optional. IMHO the ideal solution here would be that `@Input() foo!: X` would make the template compiler complain if you ever use the component without a `foo` attribute, but I don't know if it has access to enough metadata about your component class to do that. – Coderer Jun 24 '20 at 11:21
  • You should note that the use of the attribute selector (ie. `my-dir[a]`) does not work when you use `CUSTOM_ELEMENTS_SCHEMA` in your `app.module.ts` (as it allows any HTML element to be used) – Typhon May 19 '21 at 13:53
  • 1
    Beware that using this may result in some super weird behaviour during tests (in my case, calling `fixture.detectChanges` changed the provided `@Input()` values (which were made "extra-required" by providing them in the selector) to empty strings from whatever it was that I provided – l.varga Jan 10 '22 at 09:50
16

Very simple and adaptive way to declare required field

Many answers are already showing this official technique. What if you want to add multiple required fileds? Then do the following:

Single required field

@Component({
  selector: 'my-component[field1]',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.scss']
})

Multiple fields and all are required

@Component({
  selector: 'my-component[field1][field2][field3]',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.scss']
})

Multiple fields but atleast one will be required

@Component({
  selector: 'my-component[field1], my-component[field2], my-component[field3]',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.scss']
})

Here is how to use in html

<my-component [field1]="value" [field2]="value" [field3]="value"></my-component>
WasiF
  • 26,101
  • 16
  • 120
  • 128
  • 2
    You should note that the use of the attribute selector (ie. `my-component[field1]`) does not work when you use `CUSTOM_ELEMENTS_SCHEMA` in your `app.module.ts` (as it allows any HTML element to be used) – Typhon May 19 '21 at 13:54
  • 1
    It is nice, the only catch is that the error message in not intuitive. – crg Dec 20 '21 at 10:48
7

Why not use the @angular/forms library to validate your @Inputs? The following solution:

  • Fails fast (not just when the @input value is accessed by the component for the first time)
  • Allows re-using rules that you've already used for your Angular forms

Usage:

    export class MyComponent {

      @Input() propOne: string;
      @Input() propTwo: string;

      ngOnInit() {
        validateProps<MyComponent>(this, {
          propOne: [Validators.required, Validators.pattern('[a-zA-Z ]*')],
          propTwo: [Validators.required, Validators.minLength(5), myCustomRule()]
        })
      }
    }

Utility function:

    import { FormArray, FormBuilder, ValidatorFn, FormControl } from '@angular/forms';

    export function validateProps<T>(cmp: T, ruleset: {[key in keyof T]?: ValidatorFn[]} ) {
      const toGroup = {};
      Object.keys(ruleset)
        .forEach(key => toGroup[key] = new FormControl(cmp[key], ruleset[key]));
      const formGroup = new FormBuilder().group(toGroup);
      formGroup.updateValueAndValidity();
      const validationResult = {};
      Object.keys(formGroup.controls)
        .filter(key => formGroup.controls[key].errors)
        .forEach(key => validationResult[key] = formGroup.controls[key].errors);
      if (Object.keys(validationResult).length) {
        throw new Error(`Input validation failed:\n ${JSON.stringify(validationResult, null, 2)}`);
      }
    }

Stackblitz

Stephen Paul
  • 37,253
  • 15
  • 92
  • 74
  • 2
    Great solution, @Stephen Paul! I was searching for a robust solution for an internal lib and found this. Btw, I've made few modifications (less loops, variables, etc.) You can check it [**here**](https://stackblitz.com/edit/angular-ayeqp1). Thanks for sharing this :) – developer033 Feb 23 '20 at 01:28
5

Since Angular v16 you can easily use required inputs feature. Just add required option to @Input:

@Component(...)
export class MyComponent {
  @Input({ required: true }) title: string = '';
}
4

You can do it like this:

constructor() {}
ngOnInit() {
  if (!this.a) throw new Error();
}
Callum Watkins
  • 2,844
  • 4
  • 29
  • 49
Sasxa
  • 40,334
  • 16
  • 88
  • 102
  • 6
    This is wrong here, because if you *do* provide the value `0`, this will throw the error, as `0` is one of the *falsy* values in JS. the test `this.a === undefined` or `this.a == undefined` (also testing for null) would allow 0 to be given, and still make the value required. – Pac0 Jul 05 '18 at 14:56
3

I was able to make @ihor 's Required decorator work using this in the second Object.defineProperty. this forces decorator to define property on each instance.

export function Required(message?: string) {
    return function (target: Object, propertyKey: PropertyKey) {
        Object.defineProperty(target, propertyKey, {
            get() {
                throw new Error(message || `Attribute ${String(propertyKey)} is required`);
            },
            set(value) {
                Object.defineProperty(this, propertyKey, {
                    value,
                    writable: true
                });
            }
        });
    };
}
Dan Percic
  • 51
  • 2
  • 5
2

For me, I had to do it this way:

ngOnInit() {
    if(!this.hasOwnProperty('a') throw new Error("Attribute 'a' is required");
}

FYI, If you want to require @Output directives, then try this:

export class MyComponent {
    @Output() myEvent = new EventEmitter(); // This a required event

    ngOnInit() {
      if(this.myEvent.observers.length === 0) throw new Error("Event 'myEvent' is required");
    }
}
dan1st
  • 12,568
  • 8
  • 34
  • 67
Ulfius
  • 619
  • 7
  • 14
2

Here is another TypeScript decorator based approach that is less complicated and easier to understand. It also supports Component inheritance.


// Map of component name -> list of required properties
let requiredInputs  = new Map<string, string[]>();

/**
 * Mark @Input() as required.
 *
 * Supports inheritance chains for components.
 *
 * Example:
 *
 * import { isRequired, checkRequired } from '../requiredInput';
 *
 *  export class MyComp implements OnInit {
 *
 *    // Chain id paramter we check for from the wallet
 *    @Input()
 *    @isRequired
 *    requiredChainId: number;
 *
 *    ngOnInit(): void {
 *      checkRequired(this);
 *    }
 *  }
 *
 * @param target Object given by the TypeScript decorator
 * @param prop Property name from the TypeScript decorator
 */
export function isRequired(target: any, prop: string) {
  // Maintain a global table which components require which inputs
  const className = target.constructor.name;
  requiredInputs[className] = requiredInputs[className] || [];
  requiredInputs[className].push(prop);
  // console.log(className, prop, requiredInputs[className]);
}

/**
 * Check that all required inputs are filled.
 */
export function checkRequired(component: any) {

  let className = component.constructor.name;
  let nextParent = Object.getPrototypeOf(component);

  // Walk through the parent class chain
  while(className != "Object") {

    for(let prop of (requiredInputs[className] || [])) {
      const val = component[prop];
      if(val === null || val === undefined) {
        console.error(component.constructor.name, prop, "is required, but was not provided, actual value is", val);
      }
    }

    className = nextParent.constructor.name;
    nextParent = Object.getPrototypeOf(nextParent);
    // console.log("Checking", component, className);
  }
}


Mikko Ohtamaa
  • 82,057
  • 50
  • 264
  • 435
  • I think it's possible for components in different folders/modules to have the same `component.constructor.name` – Drenai May 31 '22 at 07:27