Search code examples
typescriptangularrxjsrxjs5reactive-extensions-js

Convert Array to Observable, perform transformations and return again as Array?


In ngOnInit() I have "return x" which I want to put into Observable, then perform transformations and return again in the same format.

Here is the working plunker: http://plnkr.co/edit/z26799bSy17mAL4P5MiD?p=preview

import {Component} from '@angular/core'
import { Observable } from 'rxjs'
import * as Rx from 'rxjs/Rx'

@Component({
  selector: 'my-app',
  providers: [],
  template: `
    <div>
      <h2>{{name}}</h2>

      <button (click)="addToArray()">Add</button>
      <ul>
        <li *ngFor="let item of data$ | async">{{ item }}</li>
      </ul>

    </div>
  `,
  directives: []
})
export class App {

  data = ["one","two","three"]
  data$: Observable<Array<string>>;

  constructor() {
    this.name = 'Angular2 array to observable example'
  }

  ngOnInit() {
    this.data$ = Rx.Observable.of(this.data)
      .map(data => {
        let x = data
        x.push("4")

        ///
        ///  TRANSFORM X IN THIS SECTION OF THE CODE
        ///  HERE BY PUTTING IT INTO OBSERVABLE
        ///  PERFORMING TRANSFORMATIONS AND
        ///  RETURNING THE DATA TO BE RENDERED IN TEMPLATE
        ///

        return x
      })
  }

  addToArray() {
    this.data.push('more numbers')
  }      
}

Solution

  • There is an adjusted and wirking plunker

    I would implement that with an EventEmitter and few operators, mostly

    adjusted code

      data = ["one","two","three"]
      data$: Observable<string[]>;
      protected emitter = new EventEmitter<string[]>(); 
    
      constructor() {
        this.name = 'Angular2 array to observable example'
        this.data$ = this.emitter
          .startWith(this.data)
          .scan((orig, item) => orig.concat(item))
      }
    
      ngOnInit() {
        // this.data$ = Rx.Observable.of(this.data)
        //  .map(data => {
        //    let x = data
        //    x.push("4")
        //    return x
        //  })
      }
    
      addToArray() {
        //this.data.push('more numbers')
        this.emitter.emit("forth")
      }
    

    Check it here

    EXTEND

    Much more complex plunker

    There is much more complex solution.. just profiting from Observable and its Operators. It is ready to add and delete items:

      data = ["one","two","three"]
      data$: Observable<string[]>;
      protected emitter = new EventEmitter<string[]>(); 
      protected toDelete = new Rx.BehaviorSubject<string[]>([])
        .scan((orig, item) => orig.concat(item));
    
      constructor() {
        this.name = 'Angular2 array to observable example'
        this.data$ = this.emitter
          // start
          .startWith(this.data)
          // return array
          .scan((orig, item) => orig.concat(item))
          // adjust each source string with a prefix
          .map((coll: string[]) => {
            let adjusted: string[] = []
            coll.forEach(item => {
              adjusted.push("x" + item)
            })
            return adjusted;
          })
          // now consume also array of items to be deleted
          .combineLatest(this.toDelete)
          // just those which were not delted
          .map(([all, toDelete]:[string[], string[]]) =>{
            let result = all.filter( function( el ) {
              return toDelete.indexOf( el ) < 0;
            });
            return result;
          })
      }
    
      counter: int = 0;
      addToArray() {
        this.emitter.emit(`other${++this.counter}`)
      }
    
      deleteFromArray(removeString) {
        this.toDelete.next(removeString)
      }
    

    Check it in action here

    Let's do another EXTEND

    There is a final plunker with lot of data: string\[\] array handling

    We can now even track the changes and let them adjust original data array, and even use the RESET function, to start from new begining. This is the adjusted code:

      data = ["one","two","three"]
      data$: Observable<string[]>;
      protected emitter: EventEmitter<string[]>;
      protected toDelete: Rx.BehaviorSubject<string[]>;
    
      constructor() { 
        this.initEmitters();  
        this.data$ = this.createObservable(this.data);
      }
    
      initEmitters() {
        this.emitter = new EventEmitter<string[]>(); 
        this.toDelete = new Rx.BehaviorSubject<string[]>([])
          .scan((orig, item) => orig.concat(item));
      }
    
      createObservable(initData)
      {
        let observable = this.emitter
          // start
          .startWith(initData)
          // return array
          .scan((orig, item) => orig.concat(item))
          // adjust each source string with a prefix
          .map((coll: string[]) => {
            let adjusted: string[] = []
            coll.forEach(item => {
              adjusted.push("x" + item)
            })
            return adjusted;
          })
          // now consume also array of items to be deleted
          .combineLatest(this.toDelete)
          // just those which were not delted
          .map(([all, toDelete]:[string[], string[]]) =>{
            let result = all.filter( function( el ) {
              return toDelete.indexOf( el ) < 0;
            });
            return result;
          })
    
          observable
            .subscribe((currentData) => {
              this.data.length = 0;
              [].push.apply(this.data, currentData)
            });
    
          return observable;
      }
    
      counter: int = 0;
      addToArray() {
        this.emitter.emit(`other${++this.counter}`)
      }
    
      deleteFromArray(removeString) {
        this.toDelete.next(removeString)
      }
    
      resetArray() {
        this.initEmitters();  
        this.data$ = this.createObservable(['ten','eleven'])
      }
    

    Test that array vs obesrvable in action here