I have a simple component that just renders a progress bar.
It initializes fine and the progress passes over to it just fine, but the template is not updating with the 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;
}
}
~
Elsewhere
ngOnInit() {
this.httpFileService.progress$.subscribe((progress: number) => this.onProgress(progress));
}
onProgress(progress: number) {
this.progressBar.onProgress(progress*100);
}
I feel like I am missing something very remedial.
You're going about this in a way that's working against the framework, and going to lead to much wailing and gnashing of teeth.
Right now, you're manually subscribing to an observable - httpFileService.progress$
- and then manually updating a property on a child ProgressBar
component, bypassing angular's change detection mechanism - which is why the UI isn't updating. You could manually fire change detection after setting this property and the UI would update as expected - but again, you'd be working against the framework, so let's look at how to work with it instead:
I assume that "elsewhere" is a parent of your ProgressBar
component - let's call that 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]
on the progress-bar
component: This is telling angular that there is an input property named currentProgress
on that component that should be bound to httpFileService.progress$
.
But you have now lied to angular - as it stands,ProgressBar
has no inputs at all, and angular will tell you about it when it tries to bind this nonexistent property to the given value. So we need to add the input property, and the preferred way of doing that is with the 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 critical differences here to note: First, @Input()
tells angular that currentProgress
is an input property. We've also changed the type of that property from number
to Observable<number>
- this isn't strictly necessary, but it's useful because it allows the second critical difference:
AsyncPipe
has been added to the component's pipes
, and used in both of its template bindings to currentProgress
. This is useful because it tells angular to handle all the dirtywork of subscribing to the Observable and updating the UI each time it emits a new value.
And that's all it takes: Both the width of the bar and the text above it will now update automatically to reflect the values emitted from your httpFileService
, and you didn't have to write a single line of
imperative code to make it happen.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With