Using observable, I want to filter and display a list. The input event
is fired only when the user starts typing. Therefore the list is not displayed at the first place. How can I assign a default value to the observable this.filterLocation$
until the inputEvent
starts to be triggered?
template
<ng-template ngFor let-location [ngForOf]="filterLocation$ | async">
<a mat-list-item href="#">{{location}}</a>
</ng-template>
component
ngAfterViewInit() {
const searchBox = document.querySelector('#search-input');
this.filterLocation$ = fromEvent(searchBox, 'input')
.pipe(
map((e: any) => {
const value = e.target.value;
return value ? this.locations
.filter(l => l.toLowerCase().includes(value.toLowerCase()))
: this.locations;
}),
startWith(this.locations)
)
}
}
Using startWith
makes the list to be displayed initially. But the following error is thrown:
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngForOf: null'. Current value: 'ngForOf: name1,name2'.
live code
If you want a Subject that has an 'initial value', a better RxJS class to use is BehaviorSubject. With BehaviorSubject, you pass your initial value into its constructor. Save this answer.
An ObservableValue is an entity that wraps a value and allows to observe the value for changes. In general this interface should not be implemented directly but one of its sub-interfaces ( ObservableBooleanValue etc.). The value of the ObservableValue can be requested with getValue() .
Observable in Angular is a feature that provides support for delivering messages between different parts of your single-page application. This feature is frequently used in Angular because it is responsible for handling multiple values, asynchronous programming in Javascript, and also event handling processes.
Initial value can be provided to an observable with startWith
operator, as it was already mentioned in now-deleted answer.
The problem is that filterLocation$
is assigned too late, after filterLocation$ | async
was evaluated to null
. Since the change occurs on same tick, this results in change detection error (though ExpressionChangedAfterItHasBeenCheckedError
can be considered a warning if its occurrence is expected).
The solution is to move the code from ngAfterViewInit
to ngOnInit
, before change detection was triggered.
This is not always possible. An alternative is to provide a value asynchronously, so it doesn't interfere with initial change detection.
By delaying the whole observable with delay
operator (acceptable solution for user input because it's not time critical):
this.filterLocation$ = fromEvent(searchBox, 'input')
.pipe(
map((e: any) => {
const value = e.target.value;
return value ? this.locations
.filter(l => l.toLowerCase().includes(value.toLowerCase()))
: this.locations;
}),
startWith(this.locations),
delay(0)
)
Or by making initial value asynchronous with a scheduler:
import { asyncScheduler } from 'rxjs'
...
this.filterLocation$ = fromEvent(searchBox, 'input')
.pipe(
map((e: any) => {
const value = e.target.value;
return value ? this.locations
.filter(l => l.toLowerCase().includes(value.toLowerCase()))
: this.locations;
}),
startWith(this.locations, asyncScheduler)
)
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