loading...
Angular

Angular: Setters vs ngOnChanges - which one is better?

gc_psk profile image Giancarlo Buomprisco Updated on ใƒป2 min read

Alt Text

This post was originally published on Angular Bites

Getting notified about an Angular component's property changes is normally done in 2 ways:

  • adding a setter to the property
  • using the ngOnChanges lifecycle hook

But... is there a best practice?

This discussion recently came up with my colleagues while trying to establish a standard practice in our codebase. We tried to find objective arguments to understand which one is better.

As usual, the answer depends on the scenario.

Style

Style is very much a subjective factor, but using a setter is hands-down my favorite approach. Let's take a look at a common scenario:

class MyComponent {
  private subject$ = new Subject<string>();

  @Input()
  set name(name: string) {
    this.subject$.next(name);
  }
}

It's succinct, type-safe, and encourages the usage of Observables. Not much to dislike, imho.

But can you not add a getter?

Yes. It turns out, Angular does not check the previous value by invoking the getter on the property, but stores its value in its component's logical view.

If you're interested in reading the source code where this happens, check this out.

class MyComponent implements OnChanges {
  @Input() name: string;

  private subject$ = new Subject<string>();

  ngOnChanges(changes: SimpleChanges) {
    // changes.name.currentValue is typed as `any`
    this.subject$.next(changes.name.currentValue);
  }
}

The ngOnChanges lifecycle hook, on the contrary, it's not as nice (in my opinion) - and most importantly, is weakly typed.

Also - it's worth to mention that using setters usually takes less code, which is always a good thing.

Performance

Does performance change much? At first, we thought that ngOnChanges would be more efficient as being part of Angular's lifecycle hooks, and therefore being aware of when a property changed.

It turns out, though, that Angular does only change a property when the binding is a new instance. Of course, we're taking into account the change detection being OnPush.

Performance-wise, according to my tests, there isn't a better way, and shouldn't be a factor when deciding which way to go with.

Dealing with multiple Inputs

The situation changes when taking into account changes on multiple inputs:

class MyComponent implements OnChanges {
  @Input() name: string;
  @Input() email: string;

  private username$ = new Subject<string>();

  ngOnChanges({ name, email }: SimpleChanges) {
    const username = name.currentValue || email.currentValue;
    this.username$.next(username);
  }
}

In this case, it's fairly straightforward and simpler to receive all the inputs at once.

But because this situation is pretty uncommon, and sometimes a sign of a code-smell, you'll find yourselves wanting to use the setter the majority of the time.

At the end of the day, remember that this decision is always up to you and your team's preferences.

Hope you enjoyed this post. Ciao!

Posted on by:

gc_psk profile

Giancarlo Buomprisco

@gc_psk

UI Consultant, Maker & Technical Writer. I write about JS, TS, Rx, Angular & all things Front End ๐Ÿ‡ฎ๐Ÿ‡น๐Ÿ‡ฌ๐Ÿ‡ง Follow me on Twitter: https://twitter.com/gc_psk

Angular

This is where we write about all things Angular. It's meant to be a place for Angular community and people interested in Angular and the Angular ecosystem.

Discussion

pic
Editor guide
 
  ngOnChanges({ name, email }: SimpleChanges) {
    const username = name.currentValue || email.currentValue;
    this.username$.next(username);
  } 

If you take this code in consideration, you need to pay attention, that both values are changed on the same time. If the ngOnChanges is called 2 times for each value, than this one can give some unwanted sideEffects.

 

Hi! do you mean that name or email could be undefined? TBH I'd never ship this to production - just needed a short example with multiple inputs ๐Ÿ˜…

 

Ok so if you have a component that uses your code like this

<my-component [email]="email$ | async" 
                               [username]="username$ | async"> </my-component>

If the input of the observables look like this

email$    = -E----
username$  =  ----U--

Then the ngOnChanges will be called first with only email and then with only username so one of the two is undefined. Only the values that are changed are submitted in the SimpleChanges

 

I tend to avoid setters with the inputs if the inputs have dependency on other inputs because then the order inputs to the components matter.

@Component({...})
class Component {
  @Input()
  url: Url;
  @Input()
  set users(users: Array<User>) {
    this._users = users.map((user) => {
      return user.avatar = this.url.baseUrl + user.avatar;
    });
  }
  private _users: Array<User>;
}
<component [url]="'someurl'" [users]=users /> //will work fine
<component [users]=users [url]="'someurl'" /> //will not work fine

see this link for further details: medium.com/generic-ui/a-deep-dive-...

 

Surprisingly we had this discussion as well recently. We came up with using setters as a preferred option. I donโ€™t see any major disadvantages of that approach. ๐Ÿ‘

 

Hi!

i prefer the first approach too (setter). I think that the code is definitely more clean than using the hook. Thanks for sharing your tip!

 

Great and short summary :)
Have you considered performance differences with default change detection strategy?

 

I haven't - but even then I'd be surprised if there were any noticeable differences :)