Should I write methods as arrow functions in Angular's class

In Angular it's technically possible to write class methods as ES2015 arrow functions, but I have never actually seen someone do it. Take this simple component for instance:

@Component({
  selector: 'sample'
})
export class SampleComponent {
  arrowFunction = param => {
    // Do something
  };
  normalFunction(param) {
    // Do something
  }
}

This works without any issues. Are there any differences? And why should or shouldn't I use this?


Solution 1:

The points made in this React answer are still valid in Angular, any other framework or vanilla JavaScript/TypeScript.

Class prototype methods are ES6, class arrow methods aren't. Arrow methods belong to class fields proposal and not a part of existing specs. They are implemented in TypeScript and can be transpiled with Babel as well.

It's generally preferable to use prototype method() { ... } than arrow method = () => { ... } because it's more flexible.

Callbacks

The only real opportunity that arrow method provides is that it it can be seamlessly used as a callback:

class Class {
  method = () => { ... }
}

registerCallback(new Class().method);

If prototype method should be used as a callback it should be additionally bound, this should be preferably be done in constructor:

class Class {
  constructor() {
    this.method = this.method.bind(this);
  }

  method() { ... }
}

registerCallback(new Class().method);

A decorator like bind-decorator can be used in TypeScript and ES Next to provide more concise alternative to method binding in constructor:

import bind from 'bind-decorator';

class Class {
  @bind
  method() { ... }
}

Inheritance

Arrow method restricts child classes to use arrow methods too, otherwise they won't be overridden. This creates a problem if an arrow was overlooked:

class Parent {
  method = () => { ... }
}

class Child extends Parent {
  method() { ... } // won't override Parent method
}

It's not possible to use super.method() in child class because super.method refers to Parent.prototype.method, which doesn't exist:

class Parent {
  method = () => { ... }
}

class Child extends Parent {
  method = () => {
    super.method(); // won't work
    ...
  }
}

Mixins

Prototype methods can be efficiently used in mixins. Mixins are useful for multiple inheritance or to fix problems in TypeScript method visibility.

Since arrow method isn't available on class prototype, it can't be reached from outside the class:

class Parent {
  method = () => { ... }
}

class Child extends OtherParent { ... }
Object.assign(Child.prototype, Parent.prototype) // method won't be copied

Testing

A valuable feature that prototype methods provide is that they are accessible before class instantiation, thus they can be spied or mocked in tests, even if they are called right after construction:

class Class {
  constructor(arg) {
    this.init(arg);
  }

  init(arg) { ... }
}

spyOn(Class.prototype, 'init').and.callThrough();
const object = new Class(1);
expect(object.init).toHaveBeenCalledWith(1);

This is not possible when a method is an arrow.

TL;DR: the choice between prototype and arrow class methods seems like a matter of taste, but in reality the use of prototype methods is more far-sighted. You may usually want to avoid arrow class methods, unless you are sure that they will cause no inconvenience. Don't forget to use bind on prototype methods if you pass them as callbacks.

Solution 2:

A good use case of class arrow functions are when you want to pass a function to another component and save the context of current component in the function.

@Component({

   template:`
        I'm the parent
       <child-component></child-component>

  `
})
export class PerentComponent{

   text= "default text"
   arrowFunction = param => {
    // Do something
    // let's update something in parent component ( this)

    this.text = "Updated by parent, but called by child"
  };
}

@Component({

   template:`
        I'm the child component

  `
})
export class ChildComponent{
   @Input() parentFunction;

   ngOnInit(){
      this.parentFunction.()
   }
}

 <parent-component></parent-component>

In above example, child is able to call parent component's function and text will correctly be updated, where as if I just change the parent a bit to be :

export class PerentComponent{

   text= "default text"
   arrowFunction (){
    this.text = "This text will never update the parent's text property, because `this` will be child component  "
  };
}

Solution 3:

There's just one case where you have to refrain from using arrow functions if you need to do AOT compilation, as documented here

When configuring a module, you can't use arrow functions.

❌ DONT:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Routes, RouterModule } from '@angular/router';

@NgModule({
  imports: [
    BrowserModule,
    RouterModule,
    HttpModule,
    RouterModule.forRoot([], { errorHandler: (err) => console.error(err) })
  ],
  bootstrap: [
    AppComponent
  ],
  declarations: [
    AppComponent
  ]
})
export class AppModule {}

✅ DO:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Routes, RouterModule } from '@angular/router';

function errorHandler(err) {
  console.error(err);
}

@NgModule({
  imports: [
    BrowserModule,
    RouterModule,
    HttpModule,
    RouterModule.forRoot([], { errorHandler })
  ],
  bootstrap: [
    AppComponent
  ],
  declarations: [
    AppComponent
  ]
})
export class AppModule {}