Scieur Arnaud
Scieur Arnaud

Reputation: 1017

Angular 6 : Update a value in my component when she change in my service

My goal is simple in principle, but I can not put it in place: I want one of my components to be updated when the variable of a service is changed.

To better explain my problem, here is an example :

Here, I have a service that increases or decreases a number of points. It decreases or increases this number of points when it receives a call to one of its functions. It also says if this variable 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;
  }
}

*Here, I have my component, which needs to know if my figure is even or odd.

At initialization, no problem! It knows it!

How, every time the number changes in my service (test.service.ts) then I make sure that the value pair/import changes 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;
  }

}

How should I do it ?

Did my component need to subscribe to my service in some way? Or Did I have to use one function like ngOnInit but for update?

Thanks in advance

Upvotes: 8

Views: 19813

Answers (4)

Onome Sotu
Onome Sotu

Reputation: 676

Observable is a great way to do it.

Another easy way and in my opinion much simpler is to emit an event whenever the IncreaseNumber or DecreaseNumber methods are called in the TestService.

Then, in the constructor of your TestComponent you can subscribe to the event you created in the TestService and then update your IsOdd field (or any other field you wish to update in your component). This subscription is possible because an event is a wrapper for observable.

Like so: Firstly, Create 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 Subscribe to the event in your Component constructor

    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;
      }
    }

There you go, simple and effective.

Upvotes: 1

SiddAjmera
SiddAjmera

Reputation: 39482

It would have automatically updated if these service variables were of a complex type like an Object or an Array as these are reference types. But since you have Service variables of type number and boolean, these will not be updated automatically as they are primitive types and hence passed by value.

So you'll have to use BehaviorSubjects and expose them asObservables. You'll update the values of these BehaviorSubjects by calling the next method on them. Here's how:

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);
  }
}

Now in your Component, all you need to do is subscribe to the publicly exposed Observable values 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();
  }

}

Now since you've subscribed to isOdd$ in ngOnInit which gets called during component initialization, isOdd on your Component will update every time there is a change in the isOddValue in the service.

Also since this is a custom subscription it should be assigned to a property in the Component(subscription) which would be of type Subscription which is what we get from the subscribe method as a return value. We will have to call unsubscribe on it in ngOnDestroy to avoid memory leaks.

PS: Property and method names in Angular Classes should be in lowerCamelCase according to Angular's Styleguide.

Do use lower camel case to name properties and methods.

Upvotes: 9

Explosion Pills
Explosion Pills

Reputation: 191809

There are multiple solutions you can use including setting up an Observable to subscribe to changes. These are all valid solutions.

The simplest solution would be to bind to the service in the template rather than the value:

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

In ngOnInit, you are assigning the boolean value to a property on the component. This value never changes. In order to create a data binding and respond to changes, you have to bind a reference to a property using an object which requires a . Thus, the MyService.IsOdd will work in the template.

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

Upvotes: 1

gatsbyz
gatsbyz

Reputation: 1075

For what you are saying to work, you should be increasing/decreasing the number in the service within the TestComponent, not 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;
  }

}

Upvotes: 1

Related Questions