Updating a component's value in Angular 6 when there is a change in the corresponding service

My objective sounds straightforward, but I am struggling to implement it: I want one of my components to automatically update when a variable in a service changes.

To illustrate my issue, consider the following example:

Imagine having a service that increases or decreases a points counter. The counter value is modified by calling specific functions within the service. Additionally, the service determines whether the counter value is even or odd.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class TestService {
  Number: number = 0;
  IsOdd: boolean = false;

  constructor() {}

  IncreaseNumber() {
    this.Number = this.Number + 1;
    this.IsOdd = !this.IsOdd;
  }

  DecreaseNumber() {
    this.Number = this.Number - 1;
    this.IsOdd = !this.IsOdd;
  }
}

*Now, let's consider the component which requires knowledge about whether the figure is even or odd.

Initially, everything works fine! It correctly determines the parity!

But how can I ensure that every time the number in my service (test.service.ts) changes, the even/odd value also updates in my component (test.component.ts)?*

import { Component, OnInit } from '@angular/core';
import { TestService } from '../test.service'

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss']
})

export class TestComponent implements OnInit {

  IsOdd: boolean = false;

  constructor(MyService: TestService) {}

  ngOnInit() {
    this.IsOdd = MyService.IsOdd;
  }

}

What approach should I take?

Should my component subscribe to the service somehow? Or is there a function similar to ngOnInit that I should use for updates?

Thank you in advance

Answer №1

To ensure automatic updates for service variables, they should be of a complex type like an Object or an Array which are reference types. Primitive types like number and boolean will not be updated automatically as they are passed by value.

Using BehaviorSubjects and exposing them as observables is the way to handle this. By calling the next method on the BehaviorSubjects, you can update their values effectively.

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
 providedIn: 'root'
})
export class TestService {
 private myNumberValue = 0;
 private isOddValue = false;
 private myNumber: BehaviorSubject<number> = new BehaviorSubject<number>(this.myNumberValue);
 private isOdd: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

 myNumber$: Observable<number> = this.myNumber.asObservable();
 isOdd$:  Observable<boolean> = this.isOdd.asObservable();

 constructor() {}

 increaseNumber() {
 this.myNumberValue = this.myNumberValue + 1;
 this.myNumber.next(this.myNumberValue);
 this.isOddValue = !this.isOddValue;
 this.isOdd.next(this.isOddValue);
 }

 decreaseNumber() {
 this.myNumberValue = this.myNumberValue - 1;
 this.myNumber.next(this.myNumberValue);
 this.isOddValue = !this.isOddValue;
 this.isOdd.next(this.isOddValue);
 }
}

In your Component, simply subscribe to the publicly exposed Observables from the Service:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { TestService } from '../test.service'
import { Subscription } from 'rxjs';

@Component({
 selector: 'app-test',
 templateUrl: './test.component.html',
 styleUrls: ['./test.component.scss']
})

export class TestComponent implements OnInit, OnDestroy {

 isOdd: boolean;
 subscription: Subscription;

 constructor(private testService: TestService) {}

 ngOnInit() {
 this.subscription = this.testService.isOdd$.subscribe(isOdd => this.isOdd = isOdd);
}

 ngOnDestroy() {
 this.subscription && this.subscription.unsubscribe();
}

}

By subscribing to isOdd$ in ngOnInit, any changes in isOddValue will reflect in the component's isOdd.

Remember to use lower camel case when naming properties and methods in Angular classes according to Angular's Styleguide.

Do use lower camel case to name properties and methods.

Answer №2

In order for the suggested solution to be effective, it is crucial that you adjust the number within the TestComponent's service specifically instead of altering it in other components.

import { Component, OnInit } from '@angular/core';
import { TestService } from '../test.service'

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss']
})

export class TestComponent implements OnInit {

  IsOdd: boolean = false;

  constructor(MyService: TestService) {}

  ngOnInit() {
    this.IsOdd = MyService.IsOdd;
  }

  increase() {
    MyService.IncreaseNumber();
  }

  decrease() {
    MyService.DecreaseNumber();
  }

  getIsOdd() {
    return MyService.IsOdd;
  }

}

Answer №3

Exploring various solutions is key when working with Observables, such as subscribing to changes. It's important to consider all valid options available.

A straightforward approach is to bind the service in the template instead of the value:

<div> {{ MyService.IsOdd }} </div>

When using ngOnInit, you assign a boolean value to a component property that remains constant. To establish data binding and react to changes, bind a reference to a property using an object requiring a .. Therefore, utilizing MyService.IsOdd will function correctly in the template.

https://stackblitz.com/edit/angular-rktij7

Answer №4

Using Observable is a fantastic method.

Alternatively, a straightforward approach, in my view, is to trigger an event every time the IncreaseNumber or DecreaseNumber functions are invoked within the TestService.

Subsequently, within the constructor of your TestComponent, you can subscribe to the custom event created in the TestService and update the IsOdd field (or any other desired field) in your component. This subscription is feasible because an event serves as a container for an observable.

Here's how: Firstly, establish an event emitter in your service

    import { Injectable } from '@angular/core';

    @Injectable({
      providedIn: 'root'
    })
    export class TestService {
      Number: number = 0;
      IsOdd: boolean = false;
      EmitIsOdd = new EventEmitter<any>();

      constructor() {}

      IncreaseNumber() {
        this.Number = this.Number + 1;
        this.IsOdd = !this.IsOdd;
        this.EmitIsOdd.emit();
      }

      DecreaseNumber() {
        this.Number = this.Number - 1;
        this.IsOdd = !this.IsOdd;
        this.EmitIsOdd.emit();
      }
    }

Then, within the Component constructor, subscribe to the event

    import { Component, OnInit } from '@angular/core';
    import { TestService } from '../test.service'

    @Component({
      selector: 'app-test',
      templateUrl: './test.component.html',
      styleUrls: ['./test.component.scss']
    })

    export class TestComponent implements OnInit {

      IsOdd: boolean = false;

      constructor(MyService: TestService) {
        this.MyService.EmitIsOdd.subscribe(
          () => {
            this.IsOdd = this.MyService.IsOdd;
          }    
        );
      }

      ngOnInit() {
        this.IsOdd = MyService.IsOdd;
      }
    }

That's it – straightforward and efficient.

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

Following a series of Observables in Angular 2+ in a sequential order

Apologies if this question has been answered elsewhere, I attempted to search for it but I'm not exactly sure what I should be looking for. Imagine I have this complex object: userRequest: { id: number, subject: string, ... orderIds: ...

Guide to retrieving an array of arrays in Angular 2

How do I retrieve an array of array data in Angular 2? The JSON data I have is as shown below, [[{ "pk_emp_id":5, "tenant_id":"Zone1", "location_id":1, "emp_number":"sk44", "prefix":"", "first_name":"qqqqq", "middle_name":"www", "last_nam ...

The Angular7 counterpart of the C# attribute decorator

I'm working with an API method that has an Authorize attribute to verify permissions. [Authorize(ReadIndexes)] public async Task<IActionResult> GetIndexes () { ... } Is there a similar way in Angular to implement permission checks so the API ...

Determining the data type of a property within an interface using TypeScript

Is there a way to extract the type from an interface based on its property name in order to use it in a Record? I am struggling with the syntax needed to retrieve the type by property name. My goal is to make this process more future-proof so that if the i ...

Using ngTemplateOutlet to pass ng-template to a child component in Angular 5

I am looking to develop a versatile component that can utilize custom templates for data rendering, while consolidating the business logic to prevent redundancy. Imagine this use case: a paginated list. The pagination logic should be housed within the com ...

Find the appropriate return type for a TypeScript function based on its argument

Is it feasible in TypeScript to infer the return type of a function based on its arguments? This feature would be beneficial when extracting specific properties from, for example, a database query. Here is an illustration (https://repl.it/repls/Irresponsi ...

TailwindCSS applies CSS settings from tailwind.admin.config.js without overriding tailwind.config.js. The @config directive is utilized for this purpose

I'm currently working on a project using Vite and React. I have a tailwind.admin.css file that is similar to the example provided in the documentation. @config './configs/tailwind.admin.config.js'; @tailwind base; @tailwind components; @tai ...

Angular - encountering challenges when implementing the native Web Speech API

My goal is to integrate the native Web Speech API directly without relying on any third-party libraries. I have successfully integrated the API into my service and voice recognition is functioning properly, able to generate text from recognized speech. Ho ...

Upgrade of Angular 2 to rc 5 presents with unresolved peer dependencies

I am looking to update my angular version to rc5 in order to utilize NgModule. Following the directions provided by Angular 2. I have made changes to my package.json dependencies and then executed npm stall in the terminal: ... The results from the ter ...

The object literal can only define properties that are already known, and 'data' is not found in the type 'PromiseLike<T>'

When making a request to a server with my method, the data returned can vary in shape based on the URL. Previously, I would cast the expected interface into the returned object like this: const data = Promise.resolve(makeSignedRequest(requestParamete ...

How is it that the callback method in the subscribe function of the root component gets triggered every time I navigate between different pages within the application?

I am currently using Angular 10 and have developed a server that returns an observable: export class CountrySelectionService { private _activeCountry = new BehaviorSubject(this.getCountries()[0]); public getActiveCountryPush(): Observable<CountryS ...

Is there a specific side effect that warrants creating a new Subscription?

Recently, I had a discussion on Stack Overflow regarding RxJS and the best approach for handling subscriptions in a reactive application. The debate was whether it's better to create a subscription for each specific side effect or minimize subscriptio ...

The interface 'IProduct' does not include several properties found in type 'IProduct[]', such as length, pop, push, concat, and many more

My goal is to transfer data between parent and child components using React and TypeScript. I have defined the following interfaces: export interface IProduct { id: string; name: string; price: string; image: string; ...

Creating a unified observable by merging various iterations in RXJS

For a while now, I've been grappling with the challenge of combining multiple asynchronous calls. Every time I think I'm close to figuring it out, I hit a roadblock at a foreach loop that I just can't seem to crack. Currently, my approach i ...

Updating the display in Angular 4 following modifications to an array

I am puzzled by a certain concept. I came across a notion that the view in my project only updates when some of the variables change their reference. However, I'm confused about how this applies to arrays. When I make changes to an array, sometimes th ...

What is causing pre-defined variables to act unexpectedly in Cypress?

Encountering unexpected results while pre-defining element variables and using them later in Cypress 10 with Cucumber. Let's take a look at this login test: Given("I'm logged in as Default user", () => { cy.visit('/ ...

Error: Android package not found when building a NativeScript app with TypeScript

I encountered the following error message: main-page.ts(15,26): error TS2304: Cannot find name 'android'. This error occurred after setting up a new NativeScript project using TypeScript. tns create demo --template typescript I then added the ...

Tips for ensuring proper dependency regulations in javascript/typescript/webpack

In essence, I am in search of a method to limit dependencies, similar to how one would manage different projects (libraries) in Java or C#. Think of it as friend or internal access modifiers. I'm considering various approaches to accomplish this (suc ...

When a reaction function is triggered within a context, it will output four logs to the console and

My pokemon API application is encountering some issues. Firstly, when I attempt to fetch a pokemon, it continuously adds an infinite number of the same pokemon with just one request. Secondly, if I try to input something again, the application freezes enti ...

How to dynamically inject HTML content from a child component into a different component using Angular 5

Is it possible to customize the content of a reusable header section based on the current route data in Angular? The header currently displays a title and description pulled from the route data property. My concern is how to dynamically inject different H ...