Angular 4: View is not updating after model change?

15,754

Solution 1

I believe the reason you are struggling is because it seems like you have sibling components trying to manipulate data in which neither one is truly the owner of the data. Angular tries to enforce Unidirectional Data Flow which simply means data flows from the parent to the child instead of the child to the parent. The simplest way to solve your issue would be to have parent component that "owns" the userData and the child components bind to the data to display it and emit events to manipulate it.

Using your scenario as an example:

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  public userData: any = {
    score: 2000
};

 updateScore(score: number): void {
   this.userData.score += score;
 }
}

app.component.html

<app-header [score]="userData.score"></app-header>
<app-test (onScoreChange)="updateScore($event)"></app-test>

header.component.ts

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
  @Input('score') score: number;

  constructor() { }

  ngOnInit() {
  }

}

header.component.html

<span class="topbar-details">
  Scrore : {{score}}
</span>

test.component.ts

import { Component, OnInit, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {
  @Output('onScoreChange') onScoreChange = new EventEmitter<number>();

  constructor() { }

  ngOnInit() {
  }

  ansSubmit() {
    this.onScoreChange.emit(54321);
  }
}

test.component.html

<div class="col-2">
  <button (click)="ansSubmit()" >
    <div>Submit Answer</div>
  </button>
</div>

Note: As far as loading the user information from local storage, I would use a service to handle that. Where and how the user data gets loaded is not really a concern of the components that are displaying and manipulating the data. Moving that logic to a service with the proper interface (likely returning observables if the data could ever possibly come from a remote location) will allow you to seamlessly swap out a local storage based service for an HTTP based service to force all scores to be transmitted to/from a server without changing a line of code in your component. It would also allow you to swap in a mock service for testing so local storage does not need to be seeded in order for the test to pass (i.e. - your tests have to know less about the implementation of your component to pass, so they will not be as fragile upon implementation changes)

Solution 2

You are adding HeaderComponent as a provider, so it's probably creating a new instance of the component. If you have an existing HeaderComponent, you will need to get ahold of that instance, so using a service would be helpful to keep a reference to the component instance.

It would be best to move the score to the service altogether and use a Subject to update its value. Check out their documentation: https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service. Really any strategies listed in that documentation should be useful for your situation.

Share:
15,754

Related videos on Youtube

Shubham Verma
Author by

Shubham Verma

I am a tech junky and a blogger. Worked on NodeJS, ExpressJS, AngularJS, Angular2/4/5/6, ReactJS, MySql, MongoDB, PostgreSQL etc Click here for my blogs Click here for my Youtube Channel: GuitarAnAddiction Other Blogs

Updated on June 04, 2022

Comments

  • Shubham Verma
    Shubham Verma almost 2 years

    I have two component in my app ie: HeaderComponent & TestComponent. In HeaderComponent, There is a method name setUserData(). I am calling this setUserDate() from TestComponent's method ansSubmit() which is in file test.component.ts.

    Now setUserDate() is calling and value is coming in setUserDate() method.

    The problem is : when setUserDate() calling , I am setting score value into this.userData.score, And this.userData.score value is binding in view ( HTML ) but the coming value from TestComponent's method ansSubmit() is not going to update on the view but the value is present in the ts file ( I am printing in console).

    Here is the code:

    test.component.ts:

    import { HeaderComponent } from '../../components/header/header.component';
    @Component({
      selector: 'test-page',
      templateUrl: './test.component.html',
      styleUrls: ['./test.component.css'],
      providers: [HeaderComponent]
    })
    
    export class TestComponent implements OnInit {
      private userData: any = {};
    
      constructor(private _router: Router, private headerComponent: HeaderComponent) { }
    
      ansSubmit() {
        const logged_in_user = new LocalStorage(environment.localStorageKeys.ADMIN);
        this.userData = logged_in_user.value;
        this.userData['score'] = parseInt(this.userData.score) + 54321 
    
        this.headerComponent.getUserData(this.userData['score']); // Calling HeaderComponent's method  value is 54321.
      }
    }
    

    test.component.html:

     <div class="col-2">
         <button (click)="ansSubmit()" >
              <div>Submit Answer</div>
          </button>
    </div>
    

    header.component.ts:

    import {
      Component, OnInit, ChangeDetectorRef, NgZone, ApplicationRef, ChangeDetectionStrategy
    } from '@angular/core';
    
    
    @Component({
      selector: 'app-header',
      templateUrl: './header.component.html',
      styleUrls: ['./header.component.css'],
    
    })
    
    export class HeaderComponent implements OnInit {
        public userData : any = {
          score:2000,
       };
    
      getUserData(score) { // score= 54321  ( value is coming )  
           this.userData.score = score ;
           console.log(this.userData.score);  // in this.userData.score = 54321  ( Value is assigning )
         } 
      }
    }
    

    header.component.html:

    <span class="topbar-details">
                Scrore : {{userData.score }}  // actual problem is here, Its not updating value. Its only showing 2000.
    </span>
    

    Please suggest me how to resolve this issue. Thanks in advance.

    • Sajeetharan
      Sajeetharan almost 6 years
      different component?
    • Shubham Verma
      Shubham Verma almost 6 years
      yes, I am calling this method from a different component. By using : this.headerComponent.getUserData(this.userData['score']);
    • Shashank Vivek
      Shashank Vivek almost 6 years
      can u add some code in stackblitz.com and share it
  • Shubham Verma
    Shubham Verma almost 6 years
    I tried the same, But it's not updating the old value on the view.
  • Shubham Verma
    Shubham Verma almost 6 years
    This is not a parent & child component.
  • Hyuck Kang
    Hyuck Kang almost 6 years
    Oh, did you also try to set new value to old value in callback function in this._ngZone.run?
  • dAxx_
    dAxx_ almost 6 years
    So please provide more information ... its not clear what you are trying to do and how.
  • Shubham Verma
    Shubham Verma almost 6 years
    yes, I tried this._ngZone.run, But its also not updating the model on the view.
  • Hyuck Kang
    Hyuck Kang almost 6 years
    Then, I need more information to solve it. Could you share more details?
  • Shubham Verma
    Shubham Verma over 5 years
    Hi @dAxx I have updated my question have a look and response accordingly.
  • Shubham Verma
    Shubham Verma over 5 years
    Hi @Hyuck Kang I have updated my question please have a look and response accordingly.
  • Shubham Verma
    Shubham Verma over 5 years
    I tried with ngOnChanges() but it's not working. And I don't need parent-child component, So I don't want to implement parent-child component. Both are the different component.
  • Damodharan J
    Damodharan J over 5 years
    If they are different components, you can use a angular service, inject it in your class and use it. This simplifies so many things. Have a common data in the service and use it to render in the component. Will work for even unrelated components.
  • Shubham Verma
    Shubham Verma over 5 years
    Hi @ dAxx_ It's not emitting the setUserData() when this.userDataEvent.emit(this.score); called.
  • Hyuck Kang
    Hyuck Kang over 5 years
    @Shubham Verma I updated my anwser. Please read and try it. I will wait for your feedback.
  • David
    David over 5 years
    I agree with moving the functionality to a service. Another solution would be to move the functionality to a parent component and utilize event @Output to get the render engine detection the changes properly.