Search code examples
javascriptangulartypescriptangular2-services

How to subscribe object from service in Angular 2/5?


I have issue where I want to receive data into my property component after they came from the server.

I made something like this in Service:

private events: Event[] = [];
eventChanged = new Subject<any>(); // Edit: added an observable

constructor(private http: HttpClient) { 
    this.http.get<Event[]>(this.baseUrl)
    .subscribe(events => this.events = events);
  this.eventChanged.next(this.events.slice()); //Edit: added an information to subscribers that events list changed
}
getEvents(): Observable<Event[]> {
    return this.eventChanged.asObservable();
}  // Edit: now I use this method to enables subscribers to observable

        /* I don't use that method after Edit
        showAllEvents(): Event[] {
            return [...this.events];
        }
        */

And then I use method showAllEvents() into mine component like this:

private events: Event[] = [];
private calendarEvents: CalendarEvent[] = [];
subscription: Subscription; // Edit: Added a Subscription

    getInterestedEvents() {
       // this.events = this.el.showAllEvents(); <-- I changed it into observable
   this.subscription = this.el.getEvents()
    .subscribe(
    (events) => {
      this.events = events;
    });
    this.events.forEach(eachEvent => {
        let calendarEvent: CalendarEvent = {
            start: subDays(startOfDay(new Date()), 1),
            end: addDays(new Date(), 1),
            title: eachEvent.name,
            color: colors.red
        }
        this.calendarEvents.push(calendarEvent);
    })
}

I don't know how to make this.events to wait for data from service. Any ideas? On every page it looks different and now I feel like a fool.

Edit

I made a subscribe and observable but still this.el.getEvents().subscribe... dosen't return any data in my component.


Solution

  • You can create a subscription in your service that notifies if event list changed

    eventChanged = new Subject<Event[]>();
    

    in your component, you'll want to subscribe to the eventChanged, in case event list has changed

    this.subscription = this.eventService.eventChanged
      .subscribe(
        (event: Event[]) => {
          this.events = events;
        }
      );
    

    Then add a next in your service to notify of event changed every time any component updates it, and make it resend the list of events, or whatever you want

      addEvent(event: Event) {
        this.events.push(event);
        this.eventChanged.next(this.events.slice());
      }
    

    Update: With example

    In case we have event viewer, event editor components, and event service

    Setup event service to have a subject (event changed), accessor (get events) adder (update events)

    import { Injectable } from '@angular/core';
    import {Subject} from 'rxjs/Subject';
    
    @Injectable()
    export class EventService {
    
      eventChanged = new Subject<string[]>();
    
      events: string[] = [
        'Pizza Party',
        'Hackacthon',
        'Movie Night'
      ]
      constructor() { }
    
      addEvent(event: string) {
        this.events.push(event);
        this.eventChanged.next(this.events.slice());
      }
    
      getEvents() {
        return this.events.slice();
      }
    }
    

    Then component EventViewer, gets the list, as well as subscribes for any changes

    event-viewer.component.ts

    import { Component, OnInit } from '@angular/core';
    import {EventService} from '../event.service';
    import {Subscription} from 'rxjs/Subscription';
    
    @Component({
      selector: 'app-event-viewer',
      templateUrl: './event-viewer.component.html',
      styleUrls: ['./event-viewer.component.css']
    })
    export class EventViewerComponent implements OnInit {
    
      subscription: Subscription;
      events: string[] = this.eventService.getEvents();
    
      constructor(private eventService: EventService) { }
    
    
      ngOnInit() {
        this.subscription = this.eventService.eventChanged
        .subscribe(
          (events: string[]) => {
            this.events = events;
          }
        )
      }
    
    }
    

    So then we render it out

    event-viewer.component.html

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

    Lastly we want an event editor component

    event-editor.component.ts

    import { Component, OnInit } from '@angular/core';
    import {EventService} from '../event.service';
    
    @Component({
      selector: 'app-event-edit',
      templateUrl: './event-edit.component.html',
      styleUrls: ['./event-edit.component.css']
    })
    export class EventEditComponent implements OnInit {
    
      eventNumber: number = 1;
    
      constructor(private eventService: EventService) { }
    
      ngOnInit() {
      }
    
      addEvent()
      {
        this.eventService.addEvent(this.eventNumber.toString())
        this.eventNumber++;
      }
    
    }
    

    and render a button for the user to control

    event-editor.component.ts

    <button (click)="addEvent()">Add event {{eventNumber}}</button>
    

    In the module of choice we'll obviously have to declare these components, enlist the provider

    app.module.ts

    @NgModule({
      declarations: [
        EventViewerComponent,
        EventEditComponent
      ],
      imports: [
        CommonModule
      ],
      providers: [EventService]
    })
    

    So then in then we render both components

    app.component.ts
    
    <app-event-viewer></app-event-viewer>
    <app-event-edit></app-event-edit>
    

    Now every time we click on button in one component, the other component gets the updated list enter image description here