Wayne Kaskie
Wayne Kaskie

Reputation: 3483

Angular 5 service data not updating

I am slowing migrating from AngularJS to Angular(2+) and just stumbled across something.

In AngularJS, I used a lot of factories and services for sharing data across directives, controllers and other services. It is easy to update the service in one place and have it update everywhere else automatically.

However, I am trying to use a service in a similar manner in Angular 5 and "nothing is happening" when I change the service variables.

I've seen some solutions that involve creating functions that "pull" the new data or suggestions to update the "Angular change service" to bind events to the variable.

However, my app has many variables used in many locations. It doesn't seem correct that I have to subscribe to every single variable separately, within every single component that uses them, and change the service to emit a change for every single one.

Am I just missing something?

Thanks!!! Wayne

For Example:

A component for a home page.

import { Component, OnInit } from '@angular/core';
import { HomeButtonDirective } from '../home-button.directive';

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

  constructor() { }

  ngOnInit() {
  }

}

The directive for a button that is reused on the home page:

import { Directive, Input, OnInit, HostListener } from '@angular/core';
import { InfoService } from './info.service';

@Directive({
  selector: '[appHomeButton]',
  providers: [InfoService]
})
export class HomeButtonDirective {
  @HostListener('click', ['$event']) onclick($event) {
    this.info.showHome = false;
  }
  constructor(private info: InfoService) { }

  ngOnInit() {
  }

}

The app component. Will show the home page if showHome===true:

import { Component } from '@angular/core';
import { InfoService } from './info.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  providers: [InfoService]
})
export class AppComponent {
  title = 'Testing a service';
  showHome = true;

  constructor(private info: InfoService) {
    this.showHome = this.info.showHome; // this works...showHome becomes false per the service (see below)
  }

 }

And finally, the service:

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

@Injectable()

export class InfoService {
    showHome = false;
 }

Upvotes: 1

Views: 6083

Answers (3)

JasonK
JasonK

Reputation: 5284

I had the same observations when migrating from AngularJS to Angular.

The problem

I'll use some sample code to describe the problem. Let's say we're building a CommentComponent to show comments, and the data is coming from a service named CommentService. We would then write something along the lines of:

this.comments: Array<Comment> = this.commentsService.commments;

This works perfectly fine during initialization. However, when data changes in the service (for instance when a new comment is pushed to the array), we won't see the changes reflect to our component. This is because the code above is just a direct (one-time) assignment inside the constructor, meaning there is no two-way binding.

Possible solutions

There are multiple solutions to tackle the abovementioned problem. I'll share the ones that I'm aware of, but feel free to expand the answer.


Using Observables

In this case I prefer using BehaviorSubject as it will immediately return the initial value or the current value on subscription.

private comments$: BehaviorSubject<Comment> = new BehaviorSubject([]);

... and subscribe to the observable in your component or whatever place you need the changes to be reflected:

this.commentsService.subscribe(comments => this.comments = comments);

Referencing from templates

Another solution (or workaround) is to reference to the service property directly from the template, which allows for two-way binding:

<div *ngFor="let comment of commentsService.comments">
    {{ comment | json }}
</div>

Change Detection Strategy

Another solution might be to adjust the Change Detection Strategy.

Upvotes: 1

Vikas
Vikas

Reputation: 12036

It is easy to update the service in one place and have it update everywhere else automatically.?

Register the service with an Angular module rather than a component.

from Angular docs

Angular module providers (@NgModule.providers) are registered with the application's root injector. Angular can inject the corresponding services in any class it creates. Once created, a service instance lives for the life of the app and Angular injects this one service instance in every class that needs it.

To summarize

If we want an instance of a dependency to be shared globally and share state across the application we configure it on the NgModule.

If we want a separate instance of a dependency to be shared across each instance of a component and it’s children we configure it on the components providers property.

Upvotes: 5

Nothing Mi
Nothing Mi

Reputation: 332

in my experience, the variable you said may be an object, and you change the value of its property but itself.

you should change the object itself instead of changing his property.

so, if you want to change a complex object of a service, you'd better code looks like this:

this.oldObject = anotherObject

duplicate the old object to a new one if necessary.

Hope it's helpful.

Upvotes: 0

Related Questions