Observables vs Promises in Angular
Question 1: What are the key differences between Observables and Promises?
Answer: Key differences include:
Execution
- Promises: Execute immediately when created
 - Observables: Lazy, only execute when subscribed to
 
Values
- Promises: Resolve once with a single value
 - Observables: Can emit multiple values over time
 
Cancellation
- Promises: Cannot be cancelled
 - Observables: Can be cancelled by unsubscribing
 
Operations
- Promises: Limited operations (then, catch, finally)
 - Observables: Rich set of operators for data transformation
 
Question 2: How do you use Observables and Promises in Angular applications?
Answer: Here’s a comprehensive comparison:
// Service demonstrating both approaches
@Injectable({
  providedIn: 'root'
})
export class DataService {
  private http = inject(HttpClient);
  private apiUrl = inject(API_URL);
  // Promise-based approach
  async getUserPromise(id: string): Promise<User> {
    try {
      const response = await fetch(`${this.apiUrl}/users/${id}`);
      if (!response.ok) throw new Error('User not found');
      return response.json();
    } catch (error) {
      console.error('Error fetching user:', error);
      throw error;
    }
  }
  // Observable-based approach
  getUserObservable(id: string): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/users/${id}`).pipe(
      tap(user => console.log('User fetched:', user)),
      catchError(error => {
        console.error('Error fetching user:', error);
        return throwError(() => error);
      })
    );
  }
  // Real-time updates (only possible with Observables)
  getUserUpdates(id: string): Observable<User> {
    return new Observable<User>(observer => {
      // WebSocket connection
      const ws = new WebSocket(`${this.wsUrl}/users/${id}`);
      
      ws.onmessage = event => {
        observer.next(JSON.parse(event.data));
      };
      
      ws.onerror = error => {
        observer.error(error);
      };
      
      // Cleanup on unsubscribe
      return () => ws.close();
    });
  }
}
// Component using both approaches
@Component({
  selector: 'app-user',
  template: `
    <!-- Promise approach -->
    @if (userFromPromise) {
      <div>{{ userFromPromise.name }}</div>
    }
    <!-- Observable approach -->
    @if (user$ | async; as user) {
      <div>{{ user.name }}</div>
    }
    <!-- Real-time updates -->
    @if (userUpdates$ | async; as user) {
      <div>Last updated: {{ user.lastUpdate | date:'medium' }}</div>
    }
  `
})
export class UserComponent {
  private dataService = inject(DataService);
  
  // Promise approach
  userFromPromise?: User;
  
  // Observable approach
  user$ = this.dataService.getUserObservable('123');
  
  // Real-time updates
  userUpdates$ = this.dataService.getUserUpdates('123').pipe(
    shareReplay(1)  // Share the connection
  );
  
  async ngOnInit() {
    // Promise usage
    try {
      this.userFromPromise = await this.dataService.getUserPromise('123');
    } catch (error) {
      console.error('Failed to load user:', error);
    }
  }
}Question 3: How do you convert between Promises and Observables?
Answer: Here are conversion patterns:
@Injectable({
  providedIn: 'root'
})
export class ConversionService {
  // Convert Promise to Observable
  promiseToObservable<T>(promise: Promise<T>): Observable<T> {
    return from(promise);
  }
  
  // Convert Observable to Promise
  observableToPromise<T>(observable: Observable<T>): Promise<T> {
    return firstValueFrom(observable);
  }
  
  // Example usage
  async getData() {
    // Promise to Observable
    const promise = fetch('/api/data');
    const observable$ = from(promise);
    
    // Observable to Promise
    const httpObservable$ = this.http.get('/api/data');
    const result = await firstValueFrom(httpObservable$);
    
    // Handle multiple emissions
    const values = await lastValueFrom(observable$);
  }
}Interview Tips 💡
When to Use Each
// Use Promises when: async function singleOperation() { const result = await somePromise; return process(result); } // Use Observables when: const stream$ = someObservable$.pipe( // Multiple values over time // Cancellation needed // Complex transformations required );Error Handling Patterns
// Promise error handling async function handlePromise() { try { const result = await promise; return result; } catch (error) { handleError(error); } } // Observable error handling observable$.pipe( catchError(error => { handleError(error); return EMPTY; }), retry(3) );Memory Management
export class Component implements OnDestroy { private destroy$ = new Subject<void>(); ngOnInit() { // Auto-cleanup subscriptions this.data$.pipe( takeUntil(this.destroy$) ).subscribe(); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }Performance Considerations
// Share expensive Observable operations const sharedData$ = this.getData().pipe( shareReplay(1) ); // Cache Promise results const cachedPromise = (async () => { const result = await expensiveOperation(); return result; })();Testing Strategies
describe('DataComponent', () => { // Testing Promise it('should load data from promise', async () => { const result = await component.loadDataPromise(); expect(result).toBeDefined(); }); // Testing Observable it('should stream data', (done) => { component.data$.subscribe(data => { expect(data).toBeDefined(); done(); }); }); });Common Pitfalls
// Memory leak - No unsubscribe ngOnInit() { this.data$.subscribe(); // Bad // Good - with cleanup this.data$.pipe( takeUntil(this.destroy$) ).subscribe(); } // Promise not handled somePromise(); // Bad // Good - handle or await await somePromise(); // Multiple subscriptions const data$ = this.getData().pipe( share() // Share subscription );
Remember: In interviews, focus on:
- Understanding fundamental differences
 - Knowing when to use each
 - Memory management
 - Error handling
 - Performance implications
 - Testing approaches
 - Common pitfalls and solutions
 - Real-world scenarios where one is better than the other
 
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.