Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make directive @Input required

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; } 
like image 799
Simon Trewhella Avatar asked Feb 20 '16 20:02

Simon Trewhella


People also ask

How do you make a field mandatory in Angular 8?

The ng-required directive sets the required attribute of a form field (input or textarea). The form field will be required if the expression inside the ng-required attribute returns true. The ng-required directive is necessary to be able to shift the value between true and false .

What does @input means in Angular?

Use the @Input() decorator in a child component or directive to let Angular know that a property in that component can receive its value from its parent component. It helps to remember that the data flow is from the perspective of the child component.

Can a directive have input?

Input data into a DirectiveWe can also extend or modify the behavior or functionality of a directive by inputtting data into the directive.


1 Answers

Official solution

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

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 codding 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

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' 
like image 146
Ihor Avatar answered Oct 13 '22 00:10

Ihor