Angular2 view of the component is not updated when the variable changes

I have a simple component that just displays a progress bar.

Initializes subtly, and progress proceeds to it just fine, but the template is not updated with new values.

import {Component} from 'angular2/core'; @Component({ selector : 'progress-bar', template : ` <div class="progress-container"> <div>{{currentProgress}}</div> <div [style.width.%]="currentProgress" class="progress"></div> </div> `, styles: [ '.progress-container {width: 100%; height: 5px}', '.progress {background-color: #8BC34A; height:5px}' ] }) export class ProgressBar { currentProgress: number; constructor() { this.currentProgress = 0; } onProgress(progress: number) { console.log(progress) //consoles correct percentages this.currentProgress = progress; } reset() { console.log(this.currentProgress) //is 100 this.currentProgress = 0; } } ~ 

In the other place

  ngOnInit() { this.httpFileService.progress$.subscribe((progress: number) => this.onProgress(progress)); } onProgress(progress: number) { this.progressBar.onProgress(progress*100); } 

I feel that I am missing something very corrected.

+5
source share
1 answer

You go about it in such a way as to work against the framework and lead to a big cry and gnashing of teeth.

At the moment, you manually subscribe to the observable - httpFileService.progress$ - and then manually update the property of the ProgressBar child component, bypassing the angular change detection mechanism - that’s why the user interface does not update, you can manually start the change detection after setting this property, and the user interface will be updated as expected - but again, you will work against the framework, so let's see how to work with it:

I assume that somewhere else is the parent of your ProgressBar component - call ElsewhereComponent .

 @Component({ selector: 'elsewhere', directives: [ProgressBar], template: ` <div> <progress-bar [currentProgress]="httpFileService.progress$"></progress-bar> </div> ` }) class ElsewhereComponent { // you can remove the ngOnInit and onProgress functions you posted // you also don't need a reference to the child ProgressBar component // ... whatever else you have in this class ... } 

The most important thing to note here is the addition of [currentProgress] to the progress-bar component: this tells angular that this component has an input property called currentProgress , which should be bound to httpFileService.progress$ .

But now you lied to angular - as it is, the ProgressBar has no inputs at all, and angular will tell you about it when it tries to associate this nonexistent property with the given value. Therefore, we need to add an input property, and the preferred way to do this is with Input() decorator:

 @Component({ selector : 'progress-bar', pipes: [AsyncPipe] //import this from angular2/core template : ` <div class="progress-container"> <div>{{currentProgress | async}}</div> <div [style.width.%]="currentProgress | async" class="progress"></div> </div> ` }) export class ProgressBar { @Input() currentProgress: Observable<number>; ... constructor(){ // remove the line you have in here now } } 

There are two important differences here: first, @Input() tells angular that currentProgress is an input property. We also changed the type of this property from number to Observable<number> - this is not absolutely necessary, but it is useful because it allows a second critical difference:

AsyncPipe added to the pipes component and is used in both template bindings to currentProgress . This is useful because it tells angular to handle all the dirty observable subscription capabilities and update the user interface every time it issues a new value.

And all that is required: both the width of the panel and the text above it will now be automatically updated to reflect the values ​​coming from your httpFileService , and you did not need to write a single line of imperative code for this to happen.

+11
source

Source: https://habr.com/ru/post/1247294/


All Articles