DEV Community

Arkadiusz Graczyk
Arkadiusz Graczyk

Posted on

Angular & the "Disappearing this": Why Arrow Functions are More Than Just Syntax Sugar

Let's be honest — when was the last time you typed the word function inside an Angular component? Probably never. We all reach for () => {} like it's muscle memory. In .subscribe(), in setTimeout(), inside RxJS operators — arrows everywhere.

But have you ever actually stopped mid-keystroke and thought: "wait... why do I always do this?"

Spoiler: it's not just because it's shorter.

"Let's get back to the basics — not because they're simple, but because they're hiding in plain sight."

You've probably typed () => a hundred times this week. Inside a .subscribe(), a .pipe(), a .map(). It works, you ship it, you move on. But the moment you ask "why does this actually work?" — that's where it gets fun.

So let's look at something you already know... but maybe never really noticed.


When this Has an Identity Crisis

Here's the deal with JavaScript: the value of this is dynamic. It depends on how a function gets called, not where you wrote it. And that... can mess things up.

Check this out:

@Component({
  selector: 'app-status',
  template: `<h1>Status: {{ status }}</h1>`
})
export class StatusComponent {
  status = 'Initializing...';

  updateStatus() {
    // ❌ The "Mercenary" — works for whoever calls it
    setTimeout(function() {
      // Surprise! 'this' is NOT your component anymore.
      // It's either 'undefined' (strict mode) or 'window'. Oops.
      this.status = 'Ready!'; // 💥 boom
    }, 1000);
  }

  updateStatusFixed() {
    // ✅ The "Loyal One" — remembers where it came from
    setTimeout(() => {
      // 'this' is still our component. Always.
      this.status = 'Ready!';
    }, 1000);
  }
}
Enter fullscreen mode Exit fullscreen mode

The first version? Compiles just fine. Zero warnings. But at runtime, this.status either blows up with a TypeError or quietly updates some random property on window that no one will ever read. Your template is stuck on "Initializing..." and you're staring at the screen like 🤔.

The second one just... works. Every single time.


OK Cool, But Why Does the Arrow Work?

There's a fancy term for this — Lexical Scoping — but honestly, the concept is dead simple:

Regular functions create a fresh this every time they run. When setTimeout fires your callback, it has no idea your component exists. So this becomes... well, nothing useful. undefined in strict mode, window in sloppy mode.

Arrow functions just don't bother creating their own this. They grab it from wherever they were written. In your Angular component? That's your component instance. Done.


So What's Actually Happening Behind the =>?

Today, every modern browser understands arrow functions natively — and Angular hasn't targeted ES5 in years. So no, the compiler isn't rewriting your arrows into something else.

But back in the ES5 days, it did. And looking at what it produced is the best way to understand what an arrow function actually means:

// What you write (modern):
updateStatusFixed() {
  setTimeout(() => {
    this.status = 'Ready!';
  }, 1000);
}

// What compilers used to generate (ES5):
StatusComponent.prototype.updateStatusFixed = function() {
  var _this = this;  // ← That's it. That's the whole trick.
  setTimeout(function() {
    _this.status = 'Ready!';
  }, 1000);
};
Enter fullscreen mode Exit fullscreen mode

See it? No magic. Just saving this to a variable before the callback has a chance to lose it. That's literally all an arrow function does — it just does it for you, invisibly, at the language level.

The var _this = this era is gone, but the mental model is still gold: arrow functions lock in the surrounding this so it can't be overwritten.


A Tiny History Lesson: .bind(this)

Before we had arrow functions, people solved this with .bind():

updateStatus() {
  setTimeout(function() {
    this.status = 'Ready!';
  }.bind(this), 1000);  // ← "please remember who I am"
}
Enter fullscreen mode Exit fullscreen mode

It works! But it's clunky, easy to forget, and creates an extra function every time.

Arrow functions didn't reinvent the wheel — they just made it spin easier.


"Yeah But I'd Never Use function() in setTimeout Anyway"

OK fair, that example is a bit textbook-y.

But here's the thing — this exact mechanism is quietly saving your butt every day in code you don't even think about. You've probably written this kind of thing a dozen times today:

@Component({ /* ... */ })
export class UserDashboardComponent implements OnInit {
  users: User[] = [];
  errorMessage = '';
  selectedRole = 'admin';

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http.get<User[]>('/api/users').pipe(
      map((users) => users.filter(
        (u) => u.role === this.selectedRole  // ← 'this' = component ✅
      )),
      catchError((err) => {
        this.errorMessage = err.message;     // ← 'this' = component ✅
        return of([]);
      })
    ).subscribe((users) => {
      this.users = users;                    // ← 'this' = component ✅
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Three arrows, three times this means "my component." Now picture swapping any of those => for function():

// 😬 don't try this at home
.subscribe(function(users) {
  this.users = users;  // 💥 'this' is undefined — data goes nowhere
});
Enter fullscreen mode Exit fullscreen mode

RxJS calls your subscribe callback. The Observable pipeline calls your map. The error channel triggers your catchError. None of them care about your component. The arrow function is the only reason any of those this. references work.

You knew this code worked. Now you know why it works.


"What About Signals Though?"

I can hear you: "We're moving to Signals and Zoneless Angular — is this even relevant anymore?"

Oh, 100%. Even in the shiny new Signal-based world, your state still lives inside a class. And that class still needs this:

@Component({
  selector: 'app-counter',
  template: `<h1>{{ count() }}</h1>`,
})
export class CounterComponent {
  count = signal(0);

  constructor() {
    effect(() => {
      console.log(`Count changed to: ${this.count()}`);
      //                                 ^^^^ yep, still needs 'this'
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

signal(), computed(), effect() — they all hang off this. Whether you're calling this.count.set(5) or this.count.update(v => v + 1), a stable this is what makes it all tick.

The APIs change. The fundamentals? Not so much.


Got a good this-related horror story? Drop it in the comments — I bet we've all debugged that one mysterious "Cannot read property of undefined" at least once. 😄

Top comments (0)