11

I wonder what is the "Angular/Typescript way" to set up default values for optional properties in Angular component? I am having troubles when values passed to optional properties are null or undefined.

Currently I am having something like this:

export class FooComponent implements OnInit {
  @Input() foo = 'foo';
  @Input() bar = 0;
  @Input() baz?: string;
}

If you declare default value, you dont have to specify type since it is type of assigned value and property is optional by default. This is the case of bar and foo properties.

Alternatively you can use ? to mark that this property is optional but you cant declare its default value. This is the case of baz property.

Now let us see what happens when you pass different values to those properties.

<app-foo-component
  [foo]
  [bar]="null"
  [baz]="undefined"
>
</app-foo-component>

if I console log these properties, this is what I got:

foo will be correctly 'foo'

bar will be null

baz will be undefined

Is there a elegant way to also set default values when passed values are null/undefined or do I need some checking in OnInit like this?

OnInit() {
  this.bar = this.bar || 0;
}

It feels like there is some way to do this. To me, optional property means that value could be missing, null or unset but when I want to set default value, it only works when property is missing or empty. It still set value as null or undefined in these cases and that seems to be confusing.

ConnorsFan
  • 70,558
  • 13
  • 122
  • 146
Allwe
  • 471
  • 1
  • 5
  • 18
  • Really you can declare the type, but remember that typescript transpile to javascript and the variable get the type of the value you send. e.g. you has a `@Input() mynumber:number` and you send `` – Eliseo May 06 '19 at 16:39

3 Answers3

6

Here is the PROPER best solution for this. (ANGULAR 7,8, 9)

Addressing solution: To set a default value for @Input variable. If no value passed to that input variable then It will take the default value.

Example:

I have an object interface named car:

export interface car {
  isCar?: boolean;
  wheels?: number;
  engine?: string;
  engineType?: string;
}

You made a component named app-car where you need to pass the properties of car with @input decorator of angular. But you want that isCar and Wheels properties must have a default value (ie.. isCar: true, wheels: 4)

You can set a default to that object as per below code:

export class CarComponent implements OnInit {
  private _defaultCar: car = {
    // default isCar is true
    isCar: true,
    // default wheels  will be 4
    wheels: 4
  };

  @Input() newCar: car = {};

  constructor() {}

  ngOnInit(): void {

   // this will concate both the objects and the object declared later (ie.. ...this.newCar )
   // will overwrite the default value. ONLY AND ONLY IF DEFAULT VALUE IS PRESENT

    this.newCar = { ...this._defaultCar, ...this.newCar };
   //  console.log(this.newCar);
  }
}

For more detailed article refer this.

Refer Destructuring assignment from here

Parth Developer
  • 1,371
  • 1
  • 13
  • 30
  • your solution works only if 'car' interface has all optional properties if not, you cannot set the default value for @Input as an empty object. – gesiud Apr 14 '20 at 21:18
  • 1
    Hello @gesiud , I have tested the code and worked fine when `newCar` is empty object. you can also share your **stackBlitz** link if you have any issue, I will be happy to help you. – Parth Developer Apr 15 '20 at 07:26
  • 2
    Sorry, I missed out interface declaration in your post, but anyway `@Input() newCar: car = {};` works only when all interface properties are optional, and this is not desired pattern in many cases, instead, you can omit assignment and make a declaration like so: `@Input() newCar: car `, – gesiud Apr 15 '20 at 19:35
  • 1
    If the @Input value changed after ngOnInit, the default might not apply to it correctly, we may still need setter method or ngOnChanges hook. Is my argument correct? – Jie Wang Jul 06 '20 at 06:27
  • @JieWang I am not sure about it. Looks like your argument is logical. But need to test it and then confirm. – Parth Developer Jul 08 '20 at 08:11
5

You could define bar as a getter/setter property, and make sure that the default value is applied in the setter when necessary:

private _bar = 0;

@Input() get bar() {
  return this._bar;
}
set bar(value) {
  this._bar = value || 0;
}

See this stackblitz for a demo.

ConnorsFan
  • 70,558
  • 13
  • 122
  • 146
  • I _almost_ like this. I would change one, crucial, thing in the setter. I would use a check for null instead of an `||`: `this._bar = value != null ? value : 'default';` – Alberto Chiesa Feb 13 '20 at 07:48
  • @A.Chiesa the Nullish Coalescing Operator would be more appropriate. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator – GarethAS Feb 16 '21 at 11:51
  • 1
    @GarethAS yes, if browser compatibility isn't a concern. It's a feature natively supported only in browsers released in the 2020, and it works _exactly_ like the ternary. When I commented the feature was out by 5 whole days :) – Alberto Chiesa Feb 16 '21 at 13:38
3

I have same problem when having some nested child components.

Finally this worked for me:

  @Input()
  public foo;
  get fooWithDefault() {
    return this.foo ?? 'any default value';
  }

then i used fooWithDefault property in template instead of foo.

afruzan
  • 1,454
  • 19
  • 20