angulartypescriptrxjsangular-httpclientangular-arrays

Angular 6 HttpClient assign resulting payload to array


I am attempting to do a get call in angular the call itself works as I can put a log in the subscribe and see the returned data the issue I am having is I can't seem to assign the data to my existing array (Currently Empty) and as the code below is of type Event[]. I have tried using a map on the data array which is also of type Event[] but no luck and the same with push although I believe this is because you can't push an array. I am sure there is something simple I am missing or can't find.

Here is the call I am making and bellow that the Event model.

this.httpClient.get<Event[]>('http://127.0.0.1:5555/events-get').subscribe((data) => this.events = data); 


export class Event {
    constructor(public name: String, public date: Date, public time: Date) {}
}

I am new to angular so I could be doing it all wrong any help is much appreciated.

EDIT

I have dome some more research but still no joy maybe this is something to do with having it in subscribe. I tried some of the array clone solutions from here

EDIT 2

Looking further I see that the contents of subscribe are a function is there something I am missing with scope does my this.events need passing in some way, it's set at the class level.

EDIT 3

import { Event } from '../shared/event.model';
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http'

@Injectable()
export class AdminService {
    eventsChanged = new Subject<Event[]>();

    private events: Event[] = [];

    constructor(private http: Http, private httpClient: HttpClient) {}

    getEvents() {
        this.httpClient.get<Event[]>('http://127.0.0.1:5555/events-get')
        .pipe(
            map(
                (data: Event[]) => data.map(event => {
                // may need to coerce string to Date types
                    return new Event(event.name, event.date, event.time)
                })
            )
        )
        .subscribe((events: Event[]) => this.events = events);

        console.log(this.events);
        return this.events;
}

I am then using this call in my component this hasn't changed from when it worked using a local array of Event[].

 this.events = this.adminService.getEvents();

Solution

  • The base issue is you are attempting to return the Event[] data from your AdminService.getEvents() method prior to httpClient.get<Event[]>() resolving/emitting and subscribe() executing/assigning, that is why it is always returning an empty array. This is simply the asynchronous nature of HttpClient and RxJS.

    @Injectable()
    export class AdminService {
    // ...
    
    getEvents() {
        // this happens after console.log() and return this.events
        .subscribe((events: Event[]) => this.events = events);
    
        // this executes before get()/subscribe() resolves, so empty [] is returned
        console.log(this.events);
        return this.events;
    }
    

    Instead return the get<Event[]>().pipe() instead for the @Component to call and utilize:

    import { Event } from '../shared/event.model';
    import { Injectable } from '@angular/core';
    import { Http, Response } from '@angular/http';
    import { Subject, Observable } from 'rxjs';
    import { map } from 'rxjs/operators';
    import { HttpClient } from '@angular/common/http'
    
    @Injectable()
    export class AdminService {
      eventsChanged = new Subject<Event[]>();
    
      constructor(private http: Http, private httpClient: HttpClient) {}
    
      getEvents(): Observable<Event[]> {
        return this.httpClient.get<Event[]>('http://127.0.0.1:5555/events-get')
          .pipe(
            map(
              (data: Event[]) => data.map(event => {
                // may need to coerce string to Date types
                return new Event(event.name, event.date, event.time)
               })
            )
          );
      }
    

    Component:

    @Component({ /* ... */ })
    export class EventsComponent implements OnInit {
      events$: Observable<Event[]>;
    
      constructor(private adminService: AdminService) {}
    
      ngOnInit() {
        this.events$ = this.adminService.getEvents();
      }
    }
    

    Template with async pipe:

    <ul>
      <li *ngFor="let event of events$ | async">{{event.name}}</li>
    </ul>
    

    Or:

    @Component({})
    export class EventsComponent implements OnInit {
      events: Event[] = [];
    
      constructor(private adminService: AdminService) {}
    
      ngOnInit() {
        this.adminService.getEvents()
          .subscribe(events => this.events = events);
      }
    }
    

    Template:

    <ul>
      <li *ngFor="let event of events">{{event.name}}</li>
    </ul>
    

    On a side note, HttpClient with a type will not automatically create instances of Event class from a typed get(), it is objects with a set type. You could use the RxJS map operator in combination with Array.prototype.map to create an instance of class Event for each Event typed object return from get<Event[]>. Also be careful with naming it Event as it could conflict with an existing symbol Event.

    Hopefully that helps!