I want to use an Observable<string[]> for gradually displaying content on component of my Angular apps.
export class ResultComponent implements OnInit{
message: string = 'my message for user';
spreadedMessage$: Observable<string> = from(this.message);
progressiveMessage:string = "";
ngOnInit() {
let interval = 1
this.obsMessage$
.pipe(
tap((letter) => {
delay(35*interval),
this.progressiveMessage += letter;
interval++
})
)
.subscribe();
}
}
<div> {{ progressiveMessage }} </div>
My full code listing can be found here
I tried a second approach that works well as below but I would like to understand what is wrong with my use of Observable to progress.
My alternative solution:
Changed spreadedMessage$: Observable<string> = from(this.message);
to spreadedMessage: string[] = [...this.message];
Declared this on OnInit()
method:
for (let i: number = 0; i < this.spreadedMessage.length; i++) {
setTimeout(() => (this.spaceMessager += this.spreadedMessage[i]), 35 * i);
}
Any ideas?
You are making a very common mistake that many people that are new to RxJS and Observables does; mixing imperative and reactive programming. The idea of the reactive programming is that your observable is the source of truth that the data is displayed from.
Code like
this.progressiveMessage += letter;
inside an operator where you assign the new value to another variable is an anti pattern of observables. Instead, use operators to modify and return the new value.
Since you are using Angular, the idea is that you never have to subscribe
manually in your code, but instead use the async
pipe in the template.
What you want to do is to create an observable that returns a new value every time the template should be updated.
The progressiveMessage$
in my code below is an Observable<string>
, and we assign it a value in the ngOnInit
function.
sentence: string = 'je suis un messsage qui apparait progressivement';
spreadedMessage$: Observable<string> = from(this.sentence);
progressiveMessage$: Observable<string>;
ngOnInit() {
this.progressiveMessage$ = from( // We use the from operator to get a stream from an array
this.sentence.split('') // Lets split the message up into each character for display purposes
).pipe(
// here, we first use the concatMap operator to project each source value to a new observable with the of operator
// we then delay that observable with 250ms, meaning that each observable from the letter array is delayed with 250ms
concatMap((letter) => of(letter).pipe(delay(250))),
// we use the scan operator (much like a regular Array.reduce) to iterate over the values and append the last one to the previous one
scan((acc, val) => {
// we return the value here every time a new letter arrives
return acc + val;
})
);
}
Here is a working Stackblitz example for you!
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