简体   繁体   中英

Create JSON Object multiple calls Angular 2 RXJS

I´ma little bit confused about this, but here I go:

I have information about two characters, which I get from multiple endpoints. This information is just single data that is not organised from the backend, so instead of receiving something like this:

character{
    character1{
        name: "Name of Character",
        age: "21",
        jobs: {
                job1: {
                    position:
                    company:
                },
                job2: {
                    position:
                    company;
                }
        }
    },
    character2{
        name: "Name of Character",
        age: "21",
        jobs: {
                job1: {
                    position:
                    company:
                },
                job2: {
                    position:
                    company;
                }
        }
    }   
}

I receive character name from one endpoint, character age in another and so on, for example:

.../Characters/Character_1
../Characters/Character_1/age
../Characters/Character_1/name
../Characters/Character_1/job1
../Characters/Character_1/job1/position
../Characters/Character_1/job1/company

..and separated endpoints for character 2...

../Characters/Character_2
../Characters/Character_2/age
../Characters/Character_2/name
...

From my point of view, this organisation process should be done from the backend, but sadly it is not, and that´s the way I have to collect the data.

I must not only displaying information, but sometimes I also have to count how many jobs a character has.

REMOVED FIRST EXAMPLES AS THEY WON´T WORK


STEP 1

So, as suggested I started to use forkJoin

with .../Characters/ I get the number of characters. This endpoint contains a list of endpoints urls where I need to return only those that include the word Character in theirs URL.

Note: All Endpoints return information in the next format:

{"children":["../pathOfCurrentEndpoint/path_1","../pathOfCurrentEndpoint/path_2","../pathOfCurrentEndpoint/path_3","../pathOfCurrentEndpoint/path_4"],"path":"../pathOfCurrentEndpoint","type":"Folder"}

//Component

  public characterVariable: any;

  constructor(private _service: Service) {    
    this.getCharacters();
  }

  getCharacters() {
    this._service.getAllCharacters().subscribe(
      characters => { this.characterVariable = characters;
        console.log("Json Object", this.characterVariable);
      },
      error => this.errorMessage = <any>error
    );
  }

//Service

    /**
   * This method obtains the number of characters and all the data from each one of them
   * @returns {Array} Observable array witth each Character data
   */
  getAllCharacters(): Observable<String[]> {

    let url = '../Characters';
    let subString = 'Character';
    return this.http.get(url)
      .map(this.extractPath)
      .map(data => data.filter(data => data.includes(subString)))
      //Send the two founded characters URL
      .concatMap((data) => this.getCharactersEndpoints(data))
      .catch(this.handleError);
  }

  getCharactersEndpoints(data): Observable<String[]> {
    let observables: any = [];
    let subString = 'Job';


    for (let i = 0; i < data.length; i++) {
      let url = 'urlPath/' + data[i];

      observables.push(this.http.get(url)
        .map(this.extractPath)

        //Send the "children" from each character: age, job_1, job_2, etc..
        .concatMap((data) => this.getCharactersValues(data))
        .catch(this.handleError));
    }
    return Observable.forkJoin(observables);
  }

  getCharactersValues(data): Observable<String[]> {
    let observables: any = [];

    //Get Age Value
    for (let i = 0; i < data.length; i++) {
      let url = 'urlPath/' + data[i];

      if (url.includes('Age')) {
        observables.push(this.http.get(url)
          .map(this.extractValue)
          .catch(this.handleError));
      }

      //Get Jobs Endpoints
      if (url.includes('Job')) {
        let url = 'urlPath/' + data[i];
        observables.push(this.http.get(url)
          .map(this.extractPath)
          //Send the "children" from each Job: position, company, etc..
          .concatMap((data) => this.getJobsValues(data))
          .catch(this.handleError));

      }
    }
    return Observable.forkJoin(observables);
  }


  getJobsValues(data): Observable<Number[]> {
    let observables: any = [];
    for (let i = 0; i < data.length; i++) {
      let url =  'urlPath/' + data[i];
      observables.push(this.http.get(url).map(this.extractValue).catch(this.handleError));
    }
    return Observable.forkJoin(observables);
  }


  private extractPath(res: Response) {
    let body = res.json().children;
    return body;
  }

  private extractValue(res: Response) {
    let body = res.json().value;
    return body;
  }


  private handleError(error: any) {
    let errMsg = error.message || 'Server error';
    console.error(errMsg); 
    return Observable.throw(errMsg);
  }

When I print characterVariable I get:

  Json Object 
  0:  Array[5]
      0:  21 //This is the age
      1 : Array[2]
        0 : "current position" //Current job position
        1 : "current company" //Current company
      2 : Array[2]
        0 : "previous position" //Previous job position
        1 : "previous company" //Previous company       
        ....
   1:  Array[5]
      0:  32 //This is the age
      1 : Array[2]
        0 : "current position" //Current job position
        1 : "current company" //Current company
      2 : Array[2]
        0 : "previous position" //Previous job position
        1 : "previous company" //Previous company       
        ....

I don´t know if this is the best way to organise the data. Any suggestions would be appreciated.

The next question is, where should I define that array[0][0] is the age of the first character in order to create the json Object similar to the one defined at the beginning.

Thanks.

I've made a sample with your code, seems everything fine but:

1) Are you sure that somebody called getCharacters() and getCharactersAge() methods on component

2) You can easily transform JSON data structure to array using map operator

3) Somebody should call unsubscribe. Below you can find an example of using async pipe - in that case unsubscribe will be called automatically on component destroy stage

A component in nutshell:

  @Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
  })
  export class AppComponent {
    title = 'app';

    character: any;
    character$: Observable<any>;

    errorMessage: any;

    constructor(private _service: MyNewServiceService) {
      this.getCharacters();

      // Better way - with auto usubscribe
      this.character$ = this._service.getCharacters();
    }

    getCharacters() {
      // here problem - no unsubscription !
      this._service.getCharacters().subscribe(
        (characters) => {
          this.character = characters;
        },
        (error) => {
        },
        () => {
        }
      );
    }
  }

template:

    <ul>
      {{character | json}}

      <p *ngFor="let person of character">
        <span>{{person.name}}</span>
      </p>

      <p *ngFor="let person of (character$|async)">
        <span>{{person.name}}</span>
      </p>

    </ul>

Service stub:

  import {Injectable} from '@angular/core';
  import {Observable} from 'rxjs';

  @Injectable()
  export class MyNewServiceService {

    constructor() {
    }

    getCharacters() {

      const x = {
        character1: {
          name: 'Name of Character 1',
          age: '21',
          jobs: {
            job1: {
              position: '',
              company: ''
            },
            job2: {
              position: '',
              company: ''
            }
          }
        },
        character2: {
          name: 'Name of Character 2',
          age: '21',
          jobs: {
            job1: {
              position: '',
              company: ''
            },
            job2: {
              position: '',
              company: ''
            }
          }
        }
      };

      // debugger
      // const z = Observable.of(false);
      return Observable.from([x]).map( (data) => {
        const result = [];
        Object.keys(data).forEach( key => {
          result.push(data[key]);
        });
        return result;
      });
    }
  }

You could use Observable.forkJoin to combine the data of many requests.

Single Item

const numbersObs = Observable.from(['1', '2', '3', '4']);
const lettersObs = Observable.from(['a', 'b', 'c', 'd']);
const booleanObs = Observable.from([true, true, false, false]);

Observable.forkJoin(numbersObs, lettersObs, booleanObs)
  .subscribe(result => {
    console.log("result", result);
    this.singleResult = {
        id: result[0],
        name: result[1],
        selected: result[2]
      }
  })

This will create an object:

{
  "id": "4",
  "name": "d",
  "selected": false
}

From the last emitted item of each observable.

Live plunker example

Array of items

If you wanted to create an array of objects from arrays returned from fork join you could do that as well (included in plunker example and code below) This code assumes the order is correct and that all the property arrays are the same length (each item has every property)

// If you can guarantee all the results in the correct order
Observable.forkJoin(numbersObs.toArray(), lettersObs.toArray(), booleanObs.toArray())
  .subscribe(result => {
    console.log("result", result);
    this.results = result[0].map((item, index) => {
      return {
        id: result[0][index],
        name: result[1][index],
        selected: result[2][index]
      }
    })
  })

This will create

[
  {
    "id": "1",
    "name": "a",
    "selected": true
  },
  {
    "id": "2",
    "name": "b",
    "selected": true
  },
  {
    "id": "3",
    "name": "c",
    "selected": false
  },
  {
    "id": "4",
    "name": "d",
    "selected": false
  }
]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM