Explaining the data link between a service and component: understanding Subject and BehaviorSubject

Can someone explain the concepts of Subject and BehaviorSubject in Angular to me? I'm new to Angular and struggling to understand.

I've tried searching online, but I still can't grasp how they work. The same goes for Observable and Observer - I'm having trouble understanding the underlying principle.

From what I've gathered, services are used to store global logic that is shared across multiple components (such as API interactions), while components.ts only store logic specific to that component.

For example, I'm working on a weather app with the following setup:

  • In 'weather.service.ts', I handle API requests and process the data to return an Array of Weather objects (defined by a dedicated class).
  • In 'Weather.model.ts', I define the structure of the Weather class.
  • 'search.component.ts' serves as the main component.

I passed the data from 'weather.service.ts' to 'search.component.ts' using an Observable, though I'm not sure if that was the correct approach.

Code snippet from 'weather.service.ts'

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Weather } from '../models/Weather.model';

@Injectable({
  providedIn: 'root'
})
export class WeatherService {

  APIKEY: string = "&appid=...";
  URI: string = "https://api.openweathermap.org/data/2.5/forecast?q=";

  constructor(private httpClient: HttpClient) { }

  getWeather(city): Observable<Weather[]> {
    //Fetch city data from the API:
    return this.httpClient.get(this.URI + city + this.APIKEY).pipe(map(
      (data: any) => {
        var weatherArray: Array<Weather> = [];
        for (var i = 0; i < data.list.length; i++) {
          weatherArray.push(new Weather(
            data.list[i].dt,
            data.list[i].main.temp,
            data.list[i].main.temp_min,
            data.list[i].main.temp_max,
            data.list[i].main.feels_like,
            data.list[i].main.pressure,
            data.list[i].main.humidity,
            data.list[i].weather[0].main,
            data.list[i].weather[0].icon,
            data.list[i].clouds.all,
            data.list[i].wind.speed,
            data.list[i].wind.deg
          ));
        }
        return weatherArray;
      }
    ));
  }

}

Code snippet from 'Weather.model.ts'

export class Weather {

    date: Date;
    temp: number;
    tempMin: number;
    tempMax: number;
    tempFelt: number;
    pressure: number;
    humidity: number;
    weatherDesc: string;
    weatherIcon: string;
    clouds: number;
    windSpeed: number;
    windOrientation: number;

    constructor(date: Date,
                temp: number,
                tempMin: number,
                tempMax: number,
                tempFelt: number,
                pressure: number,
                humidity: number,
                weatherDesc: string,
                weatherIcon: string,
                clouds: number,
                windSpeed: number,
                windOrientation: number) {

    }
}

Code snippet from 'search.component.ts'

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { WeatherService } from 'src/app/services/weather.service';
import { Observable } from 'rxjs';
import { Weather } from '../../models/Weather.model';

@Component({
  selector: 'app-search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit {

  searchedCity: string;
  weatherItems: Array<Weather>;

  constructor(private router: Router,
              private route: ActivatedRoute,
              private weatherService: WeatherService) { }

  ngOnInit(): void {
    //Get the city name if available:
    if (this.route.snapshot.params['city']) {
      this.searchedCity = this.route.snapshot.params['city'];
      this.weatherService.getWeather(this.searchedCity).subscribe(
        (data) => {
          this.weatherItems = data;
        }, (err) => {
          console.log(err);
        });
    } 
  }

}

Answer №1

Your solution is correct, and there is no requirement to utilize BehaviorSubject or Subject in this particular scenario.

To gain a better comprehension of the variances, you can refer to the following link:

In short, the fundamental difference lies in Observable and observer

import { Observable } from 'rxjs';

const observable = new Observable(observer => {
  setTimeout(() => observer.next('hello from Observable!'), 1000);
});

observable.subscribe(v => console.log(v));

Additionally, Subjects are derived from Observables and function as follows:

import { Subject } from 'rxjs';

const subject = new Subject();

subject.next('missed message from Subject');

subject.subscribe(v => console.log(v));

subject.next('hello from subject!');

Note that you can invoke the next method on a subject (similar action as undertaken by an observer in an observable)

Moreover, at present, it is not feasible to retrieve the most recent value submitted, which is where BehaviorSubject comes into play. It stems from Subject, retains the value immediately upon instantiation, and emits the initial value during construction

import { ReplaySubject } from 'rxjs';

const behaviorSubject = new BehaviorSubject(
  'hello initial value from BehaviorSubject'
);

behaviorSubject.subscribe(v => console.log(v));

behaviorSubject.next('hello again from BehaviorSubject');

Both Subject and BehaviorSubject offer a complete method to conclude the subscription.

Upon subscribing to BehaviorSubject, it delivers the latest value.

Enhancements in Code

In relation to your code, here are potential enhancements:

weather.model.ts

export interface Weather {

    date: Date;
    temp: number;
    tempMin: number;
    tempMax: number;
    tempFelt: number;
    pressure: number;
    humidity: number;
    weatherDesc: string;
    weatherIcon: string;
    clouds: number;
    windSpeed: number;
    windOrientation: number;

}

service

getWeather(city): Observable<Weather[]> {
    return this.httpClient.get(`${this.URI}${city}${this.APIKEY}`)
      .pipe(
         map((data: any) => data.list.map(item => ({
            date: item.dt,
            temp: item.main.temp,
            tempMin: item.main.temp_min,
            tempMax: item.main.temp_max,
            tempFelt: item.main.feels_like,
            pressure: item.main.pressure,
            humidity: item.main.humidity,
            weatherDesc: item.weather[0].main,
            weatherIcon: item.weather[0].icon,
            clouds: item.clouds.all,
            windSpeed: item.wind.speed,
            windOrientation: item.wind.deg
         }))
    );
  }

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

Organize information in a React table following a predetermined sequence, not based on alphabetical order

As a beginner with React, I'm looking to sort my data by the column "Status" in a specific order (B, A, C) and vice versa, not alphabetically. The data structure looks like this: export interface Delivery { id: number; name: string; amount: num ...

Sending information to a service from the main.ts file in an Angular application

Within my Angular CLI application, I have a main.ts file that includes the following export statement. This file serves as a microservice that receives CONTEXT from another microservice. export default { async mount({ rootElement, context }: Extension) ...

Encountering a TypeScript issue with bracket notation in template literals

I am encountering an issue with my object named endpoints that contains various methods: const endpoints = { async getProfilePhoto(photoFile: File) { return await updateProfilePhotoTask.perform(photoFile); }, }; To access these methods, I am using ...

A step-by-step guide on incorporating MarkerClusterer into a google-map-react component

I am looking to integrate MarkerClusterer into my Google Map using a library or component. Here is a snippet of my current code. Can anyone provide guidance on how I can achieve this with the google-map-react library? Thank you. const handleApiLoaded = ({ ...

Triggering an event through a shared messaging service to update the content of a component

I'm looking for a simple example that will help me understand how I can change the message displayed in my component. I want to trigger a confirmation box with *ngIf and once I confirm the change, I want the original message to be replaced with a new ...

Troubleshooting: Resolving the error message 'Unable to assign to Partial<this>' within a subclass method

If I call the base class's update method using a subclass instance, it functions as expected. However, I encounter an error when attempting to do so within a subclass method: Argument of type '{ prop: number; }' is not compatible with par ...

Data fetched using React Query

When using React Query to fetch data, the function runs smoothly. After console.logging the 'data' variable from React Query, it prints an array of objects as expected and handles states efficiently between loading, success, error. The issue ar ...

Finding the specific type within a union based on its field type

I am trying to implement a function sendCommand that returns a value of type specified by the union InputActions, selected based on the action_id type. Below is my code snippet: interface OutputAction1 { command: 'start', params: string; } i ...

Failure to invoke Jest Spy

Currently, I am attempting to conduct a test utilizing the logging package called winston. My objective is to monitor the createlogger function and verify that it is being invoked with the correct argument. Logger.test.ts import { describe, expect, it, je ...

angular index.html code displayed

I successfully deployed an Angular app on a server in the past without any issues. The Angular version is 6.1.1, Angular CLI version is 6.2.9, npm version is 6.13.4, and node version is 10.18.0 for both local and server environments. The server is running ...

Set the mat-option as active by marking it with a check symbol

Currently, I am utilizing mat-autocomplete. Whenever a selection is made manually from the dropdown options, the chosen item is displayed with a distinct background color and has a checkmark on the right side. However, when an option in the dropdown is se ...

What is the ideal method to manage API post requests in Angular?

I have a requirement to search for a document based on its name property by providing a string input from my Angular application. This involves calling a REST API endpoint with a function that resembles the following code snippet: exports.idea_search = f ...

"Maximizing Space: A Guide to Setting BootStrap Navbar on the Right Side of the Page with Dropdown Width

Take a look at the image below. There is a navbar toggle button on the upper right corner of the page. When I expand it, the dropdown items occupy the entire width of the page, causing an issue where clicking anywhere within the red box triggers the dropdo ...

Unable to update markers on agm-map for dynamic display

In my Angular 5 application, I am utilizing Angular Google Maps (https://angular-maps.com/) along with socket.io for real-time data updates of latitude and longitude from the server. Although I am successfully pushing the updated data to an array in the co ...

Unable to retrieve this information within an anonymous function

I am currently working on converting the JSON data received from an API interface into separate arrays containing specific objects. The object type is specified as a variable in the interface. Here is the interface structure: export interface Interface{ ...

Tips for extracting a particular subset from an array containing JSON objects using Angular

Hey there, I have a collection of JSON objects and I've been able to showcase them one by one using Angular (in this case, I'm building a blog). The JSON objects are retrieved through an HTTP GET request. On my landing page, I display the blog ...

Exploring the Potential of Jest in Combination with Angular 13 and Clarity Framework

I am in the process of upgrading an existing Angular application that utilizes VMware Clarity. After successfully updating from version 8.x to 10.x by following the Angular update guidelines, I encountered an issue with the jest configuration when attempti ...

Tips on how child component can detect when the object passed from parent component has been updated in Angular

In the child component, I am receiving an object from the parent component that looks like this: { attribute: 'aaaa', attribute2: [ { value }, { value }, { value }, ] } This object is passed to th ...

What could be causing the issue with the variable appearing as undefined in

My class has a property: public requestLoadPersonal: Personal[] = []; As well as a method: private filterByGender(selectedValue: any): void { console.log(this.requestLoadPersonal); this.requestLoadPersonal = this.requestLoadPersonal.filter( ...

Detecting unutilized space in a collection of divs with varying sizes using JavaScript and CSS

To better understand my issue, I created a StackBlitz demo: https://stackblitz.com/edit/angular-aqmahw?file=src/app/tiles-example.css Screenshot My tiles can have four different widths (25%, 50%, 75%, 100%). The tiles must fit on only two lines, so if a ...