End-to-End Testing with Cypress and Angular

End-to-End Testing using Cypress and Angular - A Beginner’s Guide to Flawless Apps

The Importance of End-to-End Testing for Angular Apps

E2E testing is the ultimate functionality test, simulating user interactions from start to finish. This is crucial for Angular apps because:

  • Complex behaviors: Angular handles dynamic UIs (forms, routers, APIs) that need careful inspection.
  • High user expectations: 88% of users will abandon an app after two failures
  • Faster releases: Teams using E2E testing see fewer production errors 

Cypress excels here with rapid reloading, real-time automatic waits, and a clear syntax, making it a top choice for Angular developers.

Setting Up Cypress in Your Angular Project

Prerequisites

  1. Angular CLI installed.
  2. A basic understanding of Angular components and services.

Step 1: Install Cypress

Run the following in your project directory:

npm install cypress --save-dev  

Step 2: Open Cypress

Initialize Cypress and generate boilerplate files:

npx cypress open  

Cypress creates a cypress folder with example tests.

Step 3: Configure Cypress for Angular

Update cypress.config.ts:

import { defineConfig } from 'cypress';  
export default defineConfig({  
  e2e: {  
    baseUrl: 'http://localhost:4200', // Your Angular dev server URL  
    setupNodeEvents(on, config) {},  
    specPattern: 'cypress/e2e/**/*.spec.ts',  
  },  
});  

Writing Your First E2E Test

Let's create an E2E test for a todo list app where users can add or delete tasks.

1. Create the Test File

Write cypress/e2e/todo.spec.ts:

describe('Todo List', () => {  
  beforeEach(() => {  
    cy.visit('/'); // Navigate to the app homepage  
  });  
  it('can enter a new item', () => {  
    cy.get('[data-cy=todo-input]').type('Learn Cypress{enter}');  
    cy.get('[data-cy=todo-list] li').should('have.length', 1);  
  });  
  it('will remove an item', () => {  
    cy.get('[data-cy=todo-input]').type('Delete this{enter}');  
    cy.get('[data-cy=delete-btn]').click();  
    cy.get('[data-cy=todo-list] li').should('not.exist');  
  });  
});  

2. Run the Test

npx cypress open  

Click the test file and watch Cypress execute in real-time!

5 Pro Tips for Effective Cypress Tests

  1. Use custom data attributes (data-cy=xyz) to avoid brittle selectors.
  2. Mock API Responses using cy.intercept() to test without backend dependency.
  3. Use Hooks like beforeEach to reset state between tests.
  4. Accessibility Testing: Use cy.injectAxe() and cy.checkA11y() from the cypress-axe plugin.
  5. CI/CD Integration: Run tests in headless mode:
npx cypress run --headless --browser chrome  

Most Common Cypress Pitfalls (And How to Avoid Them)

  • Dynamic elements: Use .contains() or .find() to handle changing elements.
  • Flaky tests: Ensure stable selectors, avoid timing issues, and use cy.wait() wisely.
  • Modifying app code to fit tests: Never change real code just to pass tests!

Real-World Example: Testing an Angular Auth Flow

Imagine a login process with error handling.

describe('Login Flow', () => {  
  it('should log in successfully', () => {  
    cy.visit('/login');  
    cy.get('[data-cy=email]').type('user@test.com');  
    cy.get('[data-cy=password]').type('password123{enter}');  
    cy.url().should('include', '/dashboard');  
  });  
  it('should show error message if login fails', () => {  
    cy.intercept('POST', '/api/login', { statusCode: 401 });  
    cy.visit('/login');  
    cy.get('[data-cy=username]').type('wrong@test.com');  
    cy.get('[data-cy=password]').type('wrong{enter}');  
    cy.get('[data-cy=message]').should('contain', 'Invalid username or password.');  
  });  
});  

Using Cypress for E2E testing ensures that your Angular app functions smoothly for real users. Early bug detection saves time, reduces stress, and builds user trust.

External Links:


unit Testing in Angular services

Master Unit Testing in Angular: A Beginner's Guide to Jasmine and Karma

Introduction: Why Unit Testing is Something You Can't Ignore

  • What are Jasmine and Karma?
  • How to use them properly to test both components and services.
  • Real examples (Yes, we will!) such as reading from an external data source or engaging in user interactions.
  • Helpful hints for beginners and advanced users alike.

By the end, you gain the ability to write tests that handle adversity while bringing pleasure and satisfaction every time they pass!

Jasmine & Karma: Descriptions & Connections

Jasmine: The Testing Framework

Jasmine is a behavior-driven development (BDD) framework for human-readable test writing. It uses natural language, making it easy to write and read tests.

Characteristic Features:

  • describe(): Groups related tests (test suites).
  • it(): Defines individual test cases.
  • expect(): Asserts that something will happen.

Karma: The Test Runner

Karma runs your tests in real browsers (Chrome, Firefox) or various "headless" environments. Moreover, Karma re-runs tests after changes in source code.

Why Join Them Together?

  • Jasmine writes the tests.
  • Karma executes those tests across different browsers.
  • Both integrate seamlessly with Angular's CLI.

How to Set Jasmine & Karma Up within Angular

1. Angular CLI: It's All Built In

When starting a new Angular project, Jasmine and Karma come pre-configured. Simply check the files karma.conf.js and src/test.ts to see how things are set up.

2. Running Your First Test

ng test

This command launches Karma, executes all tests, and displays a live-running browser with outputs.

Writing Your First Test

Example: Testing a Simple Service

We'll test a CalculatorService with an add() method.

Step 1: Create the Service

// calculator.service.ts
@Injectable({
  providedIn: 'root'
})
export class CalculatorService {
  add(a: number, b: number): number {
    return a + b;
  }
}

Step 2: Write the Test

// calculator.service.spec.ts
describe('CalculatorService', () => {
  let service: CalculatorService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(CalculatorService);
  });

  it('should add two numbers', () => {
    expect(service.add(2, 3)).toBe(5);
  });

  it('should handle negative numbers', () => {
    expect(service.add(-1, 5)).toBe(4);
  });
});

Step 3: Run the Test

ng test

Karma reports back with either green checks (for passing tests) or red Xs (for failures).


Real Test Scenarios

1. Components with User Interaction

This test checks if a LoginComponent emits its login event when the button is clicked.

// login.component.spec.ts
it('should emit login event on button click', () => {
  spyOn(component.login, 'emit');
  const button = fixture.nativeElement.querySelector('button');
  button.click();
  expect(component.login.emit).toHaveBeenCalled();
});

2. Mocking HTTP Requests

Testing a DataService that fetches data from an API using HttpClientTestingModule.

// data.service.spec.ts
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe('DataService', () => {
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule]
    });
    httpMock = TestBed.inject(HttpTestingController);
  });

  it('should fetch users', () => {
    const mockUsers = [{ id: 1, name: 'John' }];
    service.getUsers().subscribe(users => {
      expect(users).toEqual(mockUsers);
    });
    const req = httpMock.expectOne('/api/users');
    req.flush(mockUsers);
    httpMock.verify();
  });
});

Best Practices for Successful Testing

  1. Make each it() focused: Tests should be concise and targeted.
  2. Use Descriptive Names: it('should print error when email is invalid') > it('testing form').
  3. Mock Dependencies: Use spies or stubs to isolate tests.
  4. Prioritize Critical Paths: Focus on areas with the highest user impact.
  5. Always Run Tests: Integrate tests into CI/CD pipelines (e.g., GitHub Actions).


Angular Unit Testing Mastery: Jasmine, Karma, and Component Testing Explained

Angular Unit Testing Mastery: Jasmine, Karma, and Component Testing Explained

Angular Unit Testing Mastery: Jasmine, Karma, and Component Testing Explained

Why Component Testing Is Crucial

You’ve written a stunning Angular component. It looks perfect, but do you know if it produces the expected results?
Manual testing is too time-consuming and prone to error.

Component testing with Jasmine and Karma automates the process, allowing you to validate your UI logic and user interactions in milliseconds.

This guide includes the following topics:

  • How to test Angular components (including inputs, outputs, and DOM interactions)
  • Advanced techniques on services, HTTP calls, and user events
  • Tools such as TestBed, ComponentFixture, and Spies
  • Real-world examples to bulletproof your app.

Step by Step for Component Testing

1. Create a Simple Counter Component

Let's write a CounterComponent with increment/decrement buttons.

// counter.component.ts
@Component({
  selector: 'app-counter',
  template: ` 
    <button (click)="decrement()">-</button>
    {{ count }}
    <button (click)="increment()">+</button>
  `
})
export class CounterComponent {
  count = 0;
  increment() { this.count++; }
  decrement() { this.count--; }
}

2. Write Test for Component's Initial State, Button Click, and UI Updates

// counter.component.spec.ts
describe('CounterComponent', () => {
  let component: CounterComponent;
  let fixture: ComponentFixture<CounterComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [CounterComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(CounterComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should start with count 0', () => {
    expect(component.count).toBe(0);
  });

  it('should increment count when + is clicked', () => {
    const button = fixture.nativeElement.querySelector('button:last-child');
    button.click();
    fixture.detectChanges();
    expect(component.count).toBe(1);
    expect(fixture.nativeElement.querySelector('span').textContent).toBe('1');
  });

  it('should decrement count when - is clicked', () => {
    component.count = 5;
    fixture.detectChanges();
    const button = fixture.nativeElement.querySelector('button:first-child');
    button.click();
    expect(component.count).toBe(4);
  });
});

Take Home Points:

  • TestBed: Sets up the test environment (like NgModule).
  • ComponentFixture: Wraps component instance and template.
  • detectChanges(): Makes Angular detect changes and update the view.

Testing Component Inputs and Outputs

Example: Notification Banner Component

Test a component that takes an @Input() message and emits an @Output()on dismissal.

// notification.component.ts
@Component({
  selector: 'app-notification',
  template: `
    <div>
      {{ message }}
      <button (click)="dismiss()">Close</button>
    </div>
  `
})
export class NotificationComponent {
  @Input() message: string;
  @Output() closed = new EventEmitter<void>();

  dismiss() {
    this.closed.emit();
  }
}

Test Cases

// notification.component.spec.ts
it('should display the input message', () => {
  component.message = 'Hello!';
  fixture.detectChanges();
  const div = fixture.nativeElement.querySelector('div');
  expect(div.textContent).toContain('Hello!');
});

it('should emit event when dismissed', () => {
  component.message = 'Test';
  fixture.detectChanges();
  spyOn(component.closed, 'emit');
  const button = fixture.nativeElement.querySelector('button');
  button.click();
  expect(component.closed.emit).toHaveBeenCalled();
});

Testing Component with Dependencies

Test a UserProfileComponent that relies on a UserService

1. Mock the Dependency

// user.service.mock.ts
class MockUserService {
  getUser() {
    return of({ name: 'Alice', email: 'alice@test.com' });
  }
}

// user-profile.component.spec.ts
beforeEach(() => {
  TestBed.configureTestingModule({
    declarations: [UserProfileComponent],
    providers: [
      { provide: UserService, useClass: MockUserService }
    ]
  });

  fixture = TestBed.createComponent(UserProfileComponent);
});

2. Test Data Rendering

it('should display user data', () => {
  fixture.detectChanges(); // Triggers ngOnInit
  const nameEl = fixture.nativeElement.querySelector('.name');
  expect(nameEl.textContent).toContain('Alice');
});

Best Practices for Component Testing

  1. Test Templates and Logic: Validate both UI and component class logic.
  2. Use async/fakeAsync: Handle timers and asynchronous operations.
  3. Leverage Angular Utilities: DebugElement, By.css(), and triggerEventHandler().
  4. Isolate Tests: Mock services to avoid HTTP calls or state leaks.

Advanced Techniques

Simulating User Input with fakeAsync

Test a form input with debounce:

import { fakeAsync, tick } from '@angular/core/testing';

it('should update search term after debounce', fakeAsync(() => {
  const input = fixture.nativeElement.querySelector('input');
  input.value = 'test';
  input.dispatchEvent(new Event('input'));
  tick(300); // Fast-forward 300ms
  expect(component.searchTerm).toBe('test');
}));

Code Coverage Reports

Generate coverage stats:

ng test --code-coverage

Open coverage/index.html to see which paths lack test coverage.

Conclusion: Key Point Rating

Testing components by simulating clicks, inputs, and outputs
TestBed for module setup and dependency simulation
Combine unit tests with integration tests for full code coverage


Angular interceptors tutorial

 Angular Interceptors Explained: A Beginner’s Guide to Global HTTP Handling

The Problem with Repetitive HTTP Logic

Imagine you're creating an Angular app. Each HTTP request needs an authentication token, error handling, and logging. All the code is scattered across different services without any centralization. At this point of the game, you're smothered in byte blood—running 'head, tail, head, tail' all day long—covering your app with technical debt.

Introducing Angular Interceptors

Middleware that stands between your app and the server, interceptors allow you to modify requests, handle errors globally, and streamline tasks such as logging. This guide ensures that you replace this verbose mess of code with clear, reusable interceptors before the end of this book. Let's get started!

Angular Interceptors Explained_ A Beginner’s Guide to Global HTTP Handling

What Are Angular Interceptors?

Interceptors are classes that implement HttpInterceptor. They intercept HTTP requests or responses before the request is made to the server. Conversely, they intercept the response before it reaches your app.

Step 1: Creating Your First Interceptor

We'll make an interceptor which adds a token to the header of every HTTP request.

1a. Generate the Interceptor

ng generate interceptor auth  

1b. Implement the Intercept Method

import { Injectable } from '@angular/core';  
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';  
import { Observable } from 'rxjs';  

@Injectable()  
export class AuthInterceptor implements HttpInterceptor {  
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {  
    // Clone the request and add the auth header  
    const authToken = 'YOUR_TOKEN';  
    const authReq = req.clone({  
      setHeaders: { Authorization: `Bearer ${authToken}` }  
    });  
    return next.handle(authReq);  
  }  
}  

1c. Register the Interceptor

Add it to your app’s providers in app.module.ts:

import { HTTP_INTERCEPTORS } from '@angular/common/http';  
import { AuthInterceptor } from './auth.interceptor';  

@NgModule({  
  providers: [  
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }  
  ]  
})  
export class AppModule {}  

Now, every HTTP request from your app includes the token automatically!


Real-World Use Cases for Interceptors

1. Global Error Handling

If an HTTP request results in an error (e.g., 404, 500), intercept it and replace the message that's sent back to users with something user-friendly.

import { catchError } from 'rxjs/operators';  
import { throwError } from 'rxjs';  

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {  
  return next.handle(req).pipe(  
    catchError(error => {  
      console.error('Error:', error);  
      alert('Something went wrong!');  
      return throwError(() => error);  
    })  
  );  
}  

2. Logging HTTP Activity

To keep track of when you made requests and which URLs were addressed.

3. Modify Responses

Modify the data returned before loading it into a component.

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {  
  return next.handle(req).pipe(  
    map(event => {  
      if (event instanceof HttpResponse) {  
        return event.clone({ body: event.body.data }); // Unwrap nested data  
      }  
      return event;  
    })  
  );  
}  

Interceptor Best Practices

  1. Clone Requests: Always clone the request before modifying it to prevent side effects.
  2. Order Matters: Requests are modified in the sequence they're passed to HTTP_INTERCEPTORS.
  3. Use multi: true: Allows multiple interceptors in your app at once.
  4. Avoid Memory Leaks: Use takeUntil to unsubscribe from consumer services.

Common Pitfalls and How to Avoid Them

  • Don't make changes to the original request: Always use clone().
  • Deal with errors properly: Always return throwError after intercepting.
  • Keep Interceptors Focused: Separate interceptors for different functionalities (e.g., authentication, logging).

HTTP Requests with HttpClient in Angular

How to Make HTTP Requests with HttpClient in Angular

With the rise of modern web development, the ability to communicate with APIs has become a vital component of your Angular application. Angular has a built-in and powerful service called HttpClient for making HTTP calls.

HttpClient provides out-of-the-box methods that make it easier to:
Retrieve data from an API
Send form data
Handle errors efficiently

In this guide, we will cover:

  • How to configure and use HttpClient in Angular
  • How to deal with API calls efficiently
  • Separating out HTTP logic into a service
  • Doing subscription logic inside a component

Getting Started with HttpClient in Angular

Step 1 — Import HttpClientModule

To start using HttpClient, you first need to import the HttpClientModule in your app module.

Add HttpClientModule to app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Now, you can use HttpClient in your Angular application.

To keep the code clean and maintainable, move the API calls to an Angular service.

Step 2: Setting Up a Data Service

Run the following command to generate a new service:

ng g s data

This will create a data.service.ts file. Open the file and add the following code:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class DataService {
  private apiUrl = 'https://jsonplaceholder.typicode.com/posts';

  constructor(private http: HttpClient) {}

  getPosts(): Observable<any[]> {
    return this.http.get<any[]>(this.apiUrl);
  }
}

Why Move API Calls to a Service?

Keeps components clean and focused only on UI logic
Encourages reusability of API calls
Makes unit testing easier

Step 3: Subscribing to the API in a Component

Now, we will use the service inside a component. Update app.component.ts to subscribe to the API response:

import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
  posts: any[] = [];

  constructor(private dataService: DataService) {}

  ngOnInit(): void {
    this.dataService.getPosts().subscribe(
      (response) => {
        this.posts = response;
      },
      (error) => {
        console.error('API Error:', error);
      }
    );
  }
}

What’s Happening Here?

  • The ngOnInit() lifecycle hook ensures data is fetched when the component loads.
  • The subscribe() method listens for the API response and updates the posts array.

Step 4: Displaying Data in the Template

Now, update app.component.html to display the blog posts dynamically:

<h2>Blog Posts</h2>
<ul>
  <li *ngFor="let post of posts">
    <strong>{{ post.title }}</strong>
    <p>{{ post.body }}</p>
  </li>
</ul>

This will dynamically render the fetched posts in a simple list format.

Handling API Errors with RxJS

To gracefully handle errors, update data.service.ts to use catchError from RxJS:

import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

getPosts(): Observable<any[]> {
  return this.http.get<any[]>(this.apiUrl).pipe(
    catchError((error) => {
      console.error('API Error:', error);
      return throwError(() => new Error('Something went wrong!'));
    })
  );
}

Why Handle Errors?

Logs API failures
Provides better user experience with error messages
Prevents app crashes

Best Practices for HttpClient Usage

Move API logic to a service to keep components clean.
Use RxJS operators like catchError to handle errors.
Unsubscribe from observables when needed to prevent memory leaks.
Store API URLs in environment variables instead of hardcoding them.

By now, you should be familiar with how to make HTTP requests with HttpClient in Angular.

RxJS in Angular tutorial

Mastering RxJS in Angular: A Complete Guide to Reactive Programming"

Mastering RxJS in Angular: A Complete Guide to Reactive Programming

Introduction: Why RxJS is Crucial for Angular Developers

Angular apps thrive on reactivity — user inputs, API calls, and dynamic UI updates all happen asynchronously. Without a structured approach, handling these tasks would lead to tangled code or bugs. RxJS (Reactive Extensions for JavaScript) is Angular’s secret weapon. It turns async operations into Observables, a new, declarative way to work with async.

This Guide Will:

  • Show how deeply integrated Angular and RxJS are for HTTP, forms, and state management.
  • Cover the basics of RxJS (Observables, Operators, Subjects) in an Angular environment.
  • Step by step create things like a real-time search bar using Angular’s HttpClient.
  • Discuss the best practices to prevent memory leaks and boost performance for your Angular app.

RxJS in Angular: The Reactive Backbone

Angular’s core features leverage RxJS:

  • HTTP Requests: HttpClient methods all return Observables.
  • Reactive Forms: Follow form changes with valueChanges Observables.
  • Async Pipe: Automatically subscribe/unsubscribe in templates.
  • State Management: NgRx, for example, makes extensive use of RxJS in actions and effects.

Why RxJS + Angular?

  • Many nested callbacks can be rewritten neatly with chainable operators.
  • Using async pipe is much easier than manual unsubscriptions.
  • Real-time features like WebSockets, search, or updates are a snap.

Core Concepts for Angular Developers

1. Observables in Angular

An Observable represents a stream of data. Angular uses them extensively, as in HttpClient:

import { HttpClient } from '@angular/common/http';
@Injectable()
export class DataService {
  constructor(private http: HttpClient) {}
  // Get Observable that produces the data
  getUsers(): Observable {
    return this.http.get('/api/users');
  }
}

Subscribe to it in a Component:

updatedUsers$: Observable = this.users$.pipe(
  map(users => users.filter(u => u.age > 30)),
  shareReplay({ bufferSize: 1, refCount: false })
);

data$ = this.dataService.getUsers().pipe(
  catchError(error => of([])) // Handle mistakes gracefully
);

2. Operators for Angular Apps

Use operators to transform, filter, or combine streams:

Example: Debounce a Search Input

import { FormControl } from '@angular/forms';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';

searchControl = new FormControl('');
ngOnInit() {
  this.searchControl.valueChanges.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap(query => this.dataService.search(query))
  ).subscribe(results => this.results = results);
}

3. Subjects for Cross-Component Communication

Subjects multicast values to multiple subscribers. Use them for shared state in a service:

import { Subject } from 'rxjs';
@Injectable()
export class NotificationService {
  private notificationSubject = new Subject();
  notifications$ = this.notificationSubject.asObservable();
  sendNotification(message: string) {
    this.notificationSubject.next(message);
  }
}

// Component 1: this.notificationService.sendNotification('Hello!');
// Component 2: messages$ | async

Why RxJS + Angular? Real-World Use Cases

  • HTTP Request Cancellation: switchMap disposing of the old API calls.
  • Form Validation: valueChanges reactive response to inputs.
  • Event Handling: Delay button clicks and window resizing.

Yahoo! Case Study: Google uses RxJS on its Angular Team to manage complex asynchronous workflows in the framework itself, ensuring efficiency and scalability.

Step-by-Step: Real-Time Search with Angular and RxJS

1. Set Up the Service

@Injectable()
export class SearchService {
  constructor(private http: HttpClient) {}
  search(term: string): Observable {
    return this.http.get(`/api/search?q=${term}`);
  }
}

2. Build the Component

export class SearchComponent implements OnInit {
  constructor(private searchService: SearchService) {}
  ngOnInit() {
    this.searchControl.valueChanges.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      switchMap(term => this.searchService.search(term)),
      catchError(() => of([]))
    ).subscribe(results => this.results = results);
  }
}

3. Template with Async Pipe (Alternative)

{{ results }}
{{ item }}

Top RxJS Operators for Angular Devs

Operator Angular Use Case
switchMap Cancel previous HTTP requests
catchError Recognize HTTP errors in services
takeUntil Halt on component destruction
tap Debug or trigger side effects

Example: Unsubscribe with 'takeUntil'

private destroy$ = new Subject();
ngOnInit() {
  this.dataService.getData().pipe(
    takeUntil(this.destroy$)
  ).subscribe();
}
ngOnDestroy() {
  this.destroy$.next();
  this.destroy$.complete();
}

Angular + RxJS Best Practices

  1. Use the async Pipe: Avoid manual unsubscriptions to prevent memory leaks.
  2. Explicitly Unsubscribe: Use takeUntil or Subscription arrays if async isn’t an option.
  3. Use HttpClient: Return Observables directly from services.
  4. Never Nested Subscriptions: Use switchMap and similar operators instead.

Conclusion: Key Takeaways

  • RxJS is Angular’s Async Superpower: Use observables for HTTP, forms, and state.
  • Use Operators Wisely: switchMap, catchError, and takeUntil are vital.
  • Always Clean Up: Use async or takeUntil to handle memory leaks.

 

Select Menu