1

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')
  }      
}
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
Teddy
  • 2,277
  • 3
  • 15
  • 19
  • 1
    What is your question? – Harry Ninh Jun 24 '16 at 05:42
  • You can look at the code, there is commented out part. Variable x should be made an observable, then .map or something alike and then returned as x array, which will propagate to the template with the return statement. – Teddy Jun 24 '16 at 05:58
  • 1
    But what do you want to achieve with it? Why do you need to convert Array to Observable just to convert it back to Array? – Harry Ninh Jun 24 '16 at 06:08
  • Because I want to put that Array thru Observable machinery (rxjs) to transform the data in Array with that powerful toolset, and then when I am done, just to return it to Array which will feed the template via Observable and | async. Just before "return x" seems perfect place for that. – Teddy Jun 24 '16 at 06:52

1 Answers1

1

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

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • Thank you Radim. Point here is to change "this.data" and when changed, the observable catches it and runs thru its machinery of operators and then returns array which I can iterate over in template. When I use ".of" operator it works just like that, BUT I cant use powerful rxjs toolbox for data transformation. I tried to put it in map(x => x + " some text" ) but it holds entire Array, not the separate values, so that is impossible. If I use .from I dont get that update when I manipulate this.data. Point here is to have state in this.data, which runs thru Obs. on update and returns parsed data. – Teddy Jun 24 '16 at 06:56
  • Teddy, give a chance to my solution. Think about it. If you want to observe array (the string[] array as is) you have to create yourself lot of stuff. Events, handlers... brand new RX JS observable. My solution is a gen++. It already profits from moving that case into Observable... and then just profiting from buil-in solution. We just consume existing array with startWith. and later, any event is converted to emit with a new item. Our scan is ready for it and returning adjusted array. And we can even add more operators... please, give a chance to my way.. and it will become your way ;) ;) – Radim Köhler Jun 24 '16 at 06:58
  • Your solution is really clever, but it does not solve my problem. I looked at the code once more. What I need is: 1. change "this.data" array, 2. use ".of" to make it synchronized Observable which is subscribed to via | async in template. 3. Put somewhere between the input "this.data" and "this.data$" ability to use rxjs transforming. This is like super crazy immutable self-updating app. I just need then to update "this.data" with right data and let RxJS do its magic and I am done. Problem with yours is that dont just add items, but also need to delete, splice and other stuff from array. – Teddy Jun 24 '16 at 07:04
  • I extended my answer, created brand new, more complex plunker.. and now you have solution which is really doing a lot.. while amount of code is almost zero ;) just.. all the stuff is about observable, not about the string[]... hope that helps a bit – Radim Köhler Jun 24 '16 at 07:25
  • http://plnkr.co/edit/ExqxywBeud2gkHr41pCm?p=preview I added a function that resets the "this.data" array, which should propagate to the view. I need this function to work. I really like what you are using here, really fency high-level stuff, but I need to change this.data directly. If noone posts better answer, I will definitly accept yours. – Teddy Jun 24 '16 at 07:31
  • Focus to put only code in commented out part and make it work, then it is that. This is really hard stuff, I will offer all my reputation points when the system allows me to, just to have this solved. – Teddy Jun 24 '16 at 07:33
  • Give me sec.. if you need: **"... but I need to change this.data directly ..."** we can add just a bit more magic – Radim Köhler Jun 24 '16 at 07:49
  • I tried (my last attempt;) to give you complete solution, in the updated answer with new plunker... solving even the back this.data updating... hope that help you, and show you what Rx JS can do for us ;) – Radim Köhler Jun 24 '16 at 08:03