Skip to main content

Command Palette

Search for a command to run...

Why .bind(this) put me through hell

Updated
2 min read
H

Software developer documenting my journey through web and mobile development. I share what I learn, build, and struggle with—from React to SwiftUI, architecture decisions to deployment challenges. Not an expert, just passionate about coding and learning in public. Join me as I navigate the tech landscape one commit at a time.

The Backstory

I was peacefully implementing authentication for an Angular app, sipping my coffee, feeling productive. I added a simple tap operator to my login method to store the JWT token in localStorage. Easy-peasy, right? Wrong. The injected StorageService kept showing up as undefined despite being properly imported and injected. Cue the hair-pulling.

The Problem

The issue occurred in an authentication service that looked something like this:

@Injectable()
export class AuthService {
  constructor(
    private http: HttpClient,
    private storageService: StorageService // Properly injected!
  ) {}

 private handleLoginSuccess(response: AuthResponse): void {
  // ERROR: this.storageService is undefined here!
  this.storageService.setItem('token', response.token);
}

  login(credentials: {username: string, password: string}): Observable<AuthResponse> {
    return this.http.post<AuthResponse>('/api/login', credentials).pipe(
      tap(this.handleLoginSuccess)
    );
  }
}

When the tap operator executed, this.storageService was mysteriously undefined, despite being available in other methods. It's like my service got amnesia, but only for this one subscription.

The Insight

The culprit? Context binding! Inside the tap callback, this wasn't referring to my service instance anymore. The solution was elegantly simple:

login(credentials: {username: string, password: string}): Observable<AuthResponse> {
  return this.http.post<AuthResponse>('/api/login', credentials).pipe(
    // The magic: .bind(this) preserves the correct context
    tap(this.handleLoginSuccess.bind(this))
  );
}

private handleLoginSuccess(response: AuthResponse): void {
  // Now this.storageService exists!
  this.storageService.setItem('token', response.token);
}

By using .bind(this), I explicitly told JavaScript: "Hey, when you execute this function later, remember that this should refer to the current context, not whatever context exists when the function runs."

Why It Matters

This isn't just an Angular issue—it's a fundamental JavaScript concept that applies to any framework. Arrow functions automatically bind this, which is why you'll often see:

tap(response => this.handleLoginSuccess(response))

But using .bind(this) with a method reference can make your code cleaner while properly maintaining context. It's especially important in:

  • Observable operators like tap, map, and filter

  • Event handlers

  • Callback functions

  • setTimeout/setInterval calls

TL;DR

When your injected services go missing inside callbacks, remember: .bind(this) is your context-preserving superhero.

today i learned

Part 4 of 6

Daily code discoveries from my developer journey. Each post delivers concise, actionable insights I've uncovered while building with React, Typescript, and more. Brief but impactful lessons for curious developers looking to grow incrementally.

Up next

Fixing the Mysterious 404 When Deploying Angular 19 to Vercel

TL;DR When deploying Angular 19 apps to Vercel, override the output directory setting to dist/[your-app-name]/browserto avoid the dreaded 404 page if you use the @angular-devkit/build-angular:application builder The Backstory There I was, feeling acc...

More from this blog

C

Code Craft by Hermann Kao | Fullstack Development Learning Journey & Tutorials

22 posts

Following my journey through web and mobile development—sharing insights, challenges, and lessons learned as I build with React, SwiftUI, and more. Learning in public, one commit at a time.