Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Progressive filling with Observable

I want to use an Observable<string[]> for gradually displaying content on component of my Angular apps.

  1. On TypeScript side I declared this:
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();
    }
}
  1. Binding progressiveMessage to the template

<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:

  1. Changed spreadedMessage$: Observable<string> = from(this.message); to spreadedMessage: string[] = [...this.message];

  2. 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?

like image 324
Pierre ETRILLARD Avatar asked Sep 02 '25 13:09

Pierre ETRILLARD


1 Answers

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!

like image 195
Daniel B Avatar answered Sep 05 '25 15:09

Daniel B