Angular: Exploring the most effective strategies for optimizing code within the ".subscribe()" method

Imagine having a component structured like this

The Initial Code:

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<pre>{{ response | json }}</pre>`,
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  constructor(private _http: HttpClient) {
    this.fetchUsers();
  }

  response!: any;

  fetchUsers() {
    this._http.get(`https://jsonplaceholder.typicode.com/users`).subscribe(
      (resp) => {
        console.log(resp);
        this.response = resp;
      },
      (err) => {
        console.error(err);
        this.response = err;
      },
      () => {
        console.log('Subscription Complete');
      }
    );
  }
}

and now we have various methods to restructure the code...

Refactoring Method 1:

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<pre>{{ response | json }}</pre>`,
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  constructor(private _http: HttpClient) {
    this.fetchUsers();
  }

  response!: any;

  onSubscribe = (resp: any) => {
    console.log(resp);
    this.response = resp;
  };

  onError = (err: any) => {
    console.error(err);
    this.response = err;
  };

  onCompletion = () => {
    console.log('Subscription Complete');
  };

  fetchUsers() {
    this._http
      .get(`https://jsonplaceholder.typicode.com/users`)
      .subscribe(this.onSubscribe, this.onError, this.onCompletion);
  }
}

Refactoring Method 2:

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<pre>{{ response | json }}</pre>`,
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  constructor(private _http: HttpClient) {
    this.fetchUsers();
  }

  response!: any;

  onSubscribe(resp: any) {
    console.log(resp);
    this.response = resp;
  }

  onError(err: any) {
    console.error(err);
    this.response = err;
  }

  onCompletion() {
    console.log('Subscription Complete');
  }

  fetchUsers() {
    this._http.get(`https://jsonplaceholder.typicode.com/users`).subscribe(
      (resp) => this.onSubscribe(resp),
      (err) => this.onError(err),
      () => this.onCompletion()
    );
  }
}

Refactoring Method 3:

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<pre>{{ response | json }}</pre>`,
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  constructor(private _http: HttpClient) {
    this.fetchUsers();
  }

  response!: any;

  fetchUsers() {
    this._http.get(`https://jsonplaceholder.typicode.com/users`).subscribe({
      next: (resp: any) => {
        console.log(resp);
        this.response = resp;
      },
      error: (err: any) => {
        console.error(err);
        this.response = err;
      },
      complete: () => {
        console.log('Subscription Complete');
      },
    });
  }
}

Refactoring Method 4:

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<pre>{{ response | json }}</pre>`,
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  constructor(private _http: HttpClient) {
    this.fetchUsers();
  }

  response!: any;

  onSubscribe = (resp: any) => {
    console.log(resp);
    this.response = resp;
  };

  onError = (err: any) => {
    console.error(err);
    this.response = err;
  };

  onCompletion = () => {
    console.log('Subscription Complete');
  };

  fetchUsers() {
    this._http.get(`https://jsonplaceholder.typicode.com/users`).subscribe({
      next: this.onSubscribe,
      error: this.onError,
      complete: this.onCompletion,
    });
  }
}

Refactoring Method 5:

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<pre>{{ response | json }}</pre>`,
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  constructor(private _http: HttpClient) {
    this.fetchUsers();
  }

  response!: any;

  onSubscribe(resp: any) {
    console.log(resp);
    this.response = resp;
  }

  onError(err: any) {
    console.error(err);
    this.response = err;
  }

  onCompletion() {
    console.log('Subscription Complete');
  }

  fetchUsers() {
    this._http.get(`https://jsonplaceholder.typicode.com/users`).subscribe({
      next: (resp: any) => this.onSubscribe(resp),
      error: (err: any) => this.onError(err),
      complete: () => this.onCompletion(),
    });
  }
}

The question arises:

With performance as the top priority and readability following that - which refactoring approach would be most suitable?

  1. Refactoring Method 1?
  2. Refactoring Method 2?
  3. Refactoring Method 3?
  4. Refactoring Method 4?
  5. Refactoring Method 5?
  6. The Initial Code?

Answer №1

In my opinion, I would restructure the code to use a more declarative approach

  1. Start by creating a service that handles all data access operations.
  2. Within the service, define a variable (not a method) to handle the Observable returned from the HTTP GET request.
  3. In the component, declare a variable (outside of the constructor or lifecycle hooks) to manage the Observable returned from the service.
  4. Utilize the async pipe within the template.

Sample Service:

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

@Injectable({
  providedIn: 'root',
})
export class UserService {
  users$ = this.http
    .get(`https://jsonplaceholder.typicode.com/users`)
    .pipe(tap((response) => console.log(response)));

  constructor(private http: HttpClient) {}

  fetchUsers() {}
}

Sample Component/template

import { Component } from '@angular/core';
import { catchError } from 'rxjs';
import { UserService } from './user.service';

@Component({
  selector: 'my-app',
  template: `<pre>{{ users$ | async | json }}</pre>`,
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  errorMessage = '';

  users$ = this.userService.users$.pipe(
    catchError(err => this.errorMessage = err)
  )

  constructor(private userService: UserService) { }

}

For further insights on this declarative programming pattern, check out: https://youtu.be/0XPxUa8u-LY

Answer №2

In my opinion, the most effective approach would be to go with number 4 for the following reasons:

When it comes to readability, passing three arguments in a subscription (next, error, complete) is considered outdated in RxJS as function arguments can lead to code that is difficult to understand. More information on this topic can be found here

Rather than using individual function arguments, RxJS suggests utilizing a JavaScript object where you can clearly define the callbacks.

({next: () =>{}, error: () =>{}, complete: () =>{})

Furthermore, breaking down the code into functions can significantly improve its readability.

In terms of performance, by passing a JS object as an argument to the subscription, RxJS automatically assigns empty functions to any callback that is not explicitly defined, saving you from having to do it manually.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilizing ngModel within a ngFor iteration

Utilizing ngModel within an ngFor loop to extract data from a dropdown menu goes as follows: <div *ngFor="let group of groups"> <select [(ngModel)]="selectedOption"> <option *ngFor="let o of options" ...

Using the novalidate attribute in Angular 4.0.0

Since migrating to angular 4, I have encountered a peculiar issue with my template-driven form. The required attribute on the input field appears to be malfunctioning. It seems like the form has the novalidate attribute by default, preventing HTML5 validat ...

Having trouble setting the default value of a select element with 'selected' in Angular's bootstrap?

Click here I've been facing some difficulties in making the 'selected' tag work to pre-select my default select value. It seems like it might be related to the unique pipe I'm using and how Angular handles it. I have experimented with ...

Is it possible to indicate the base type for a generic function?

Is it possible to define the generic type T as an Object rather than a primitive type like number or string? For example, this clone function should only accept Objects as input. It will destructure the input object o, set its prototype back to that of th ...

the value of properrty becomes undefined upon loading

In my code, there exists an abstract class named DynamicGridClass, containing an optional property called showGlobalActions?: boolean?. This class serves as the blueprint for another component called MatDynamicGridComponent, which is a child component. Ins ...

The art of toggling an ion-item effortlessly through a simple click of a button

I'm working on an Ionic project where I need to implement a button that displays items when clicked, and hides them when clicked again. However, using ShowDisplay() with *ngIf doesn't load anything. Is there a way to modify my DisplayF1() functio ...

Typing in a number will always activate the change event

Having trouble with Angular's change event on numeric input? It doesn't always trigger when clicking the increment or decrement buttons - it only triggers once and then requires the input to lose focus before triggering again. Is there a way to ...

Understanding how to extract and utilize the display name of enums from a C# Web API within an Angular

Within my C# web API, I have established an enum with a designated display name: public enum Countries { Australia, [Display(Name="New Zealand")] NewZealand } To showcase this list in a dropdown menu within my Angular project, I transmit ...

Running `ng start angularProject` or `npm install` will generate additional files in the project's

I'm encountering a major issue where every time I create a new project using 'ng start' or run 'npm install', extra files are being generated in the root folder like this. https://i.stack.imgur.com/BUphZ.png Here is my package.js ...

Error in AWS Cloud Development Kit: Cannot access properties of undefined while trying to read 'Parameters'

I am currently utilizing aws cdk 2.132.1 to implement a basic Lambda application. Within my project, there is one stack named AllStack.ts which acts as the parent stack for all other stacks (DynamoDB, SNS, SQS, StepFunction, etc.), here is an overview: im ...

Validate uniqueness of input in database using Angular's async validator

My <input> element allows users to enter a value that should be unique in the database. I'm looking for a way to validate this input dynamically on the front-end, and display an error message if the value is already in the database. ...

My goal is to eliminate the console error message "@ Angular: GET http://localhost:4200/assets/i18n/1/fr.json 404 (Not Found)" related to missing file

Is there a way to prevent the "GET http://localhost:4200/assets/i18n/1/fr.json 404 (Not Found)" error from appearing in both the console and network of the browser while using Angular? I need a solution for this.custom.translate.loader.ts****** return Obse ...

Creating number inputs in Ionic 2/3 using alerts

I am currently working with Ionic 3.x on my macOS system. The issue I am facing is as follows: I have an array that contains a number and another array consisting of names. table: { number: number, names: string[] } = { number: 0, names: ['& ...

The dynamic fusion of Typescript and Angular 2 creates a powerful

private nodes = []; constructor(private nodeService: NodeService) {} this.nodeService.fetchNodes('APIEndpoint') .subscribe((data) => { this.nodes.push(data); }); console.log(this.nodes) This ...

Cannot upload the same file to both Spring and Angular twice

Having trouble uploading the same file twice. However, it works fine when uploading different files. Encountering an error under the Network tab in Chrome { timeStamp: ......, status: 417 error: 'Bad Request', message: 'Required reques ...

Angular 4: Automatically dismiss modal once form submission process is complete

My Bootstrap 4 modal closes properly when I press the close button. However, I want the modal to close after submitting the create button in the form. I am using Angular 4. <div class="modal fade" id="createLabel" tabindex="-1" role="dialog" aria-label ...

Challenge with sharing an array from a different component in Angular using @Input()

Recently, I started learning Angular and decided to create a basic blog application. While trying to retrieve a property from another component using the @Input() decorator, I encountered an issue specifically related to arrays. In a post-list-item compone ...

Obtaining the correct information from an array using Ionic's Angular framework

I am currently working with an array of data that contains arrays within each item. I have been able to display the data as needed, except for the IDs. Approach Show arrays within the array Retrieve the IDs of the arrays (excluding the IDs inside the ar ...

Utilizing Typescript within Visual Studio Code alongside node_modules

I currently have typescript installed and am utilizing the powerful visual code editor. Whenever I attempt to navigate to the definition of a typescript function in the node_modules directory, Visual Studio Code ends up expanding the entire 'node_mod ...

RXJS - Trigger a function based on a specific condition being fulfilled by a value emitted from an observable

I have created a search field with autocomplete functionality. By using an observable that monitors changes in the text field, I am able to trigger actions based on user input. this.term.valueChanges .debounceTime(300) .distinctUntilChange ...