cha-cha
cha-cha

Reputation: 338

Angular2: Updating and sharing data between components

I'm trying to make something that looks like this: enter image description here

Looking for a way to make Service feed the latest data to subscribed components whenever its necessary. For example: Component3 writes changes to the API > Component1 and Component2 have to get the latest data.

Currently both components are subscribed to an observable returned by the service and this causes 2 API requests if both are rendered. This feels wrong. In my understanding these components should be fed the data and not request it on their own. To be clear: the data returned by this service is always being used in Component1 while Component2 is rendered optionally.

Here's some snippets and a bit of description of what I've done so far:

Service gets data through HTTP from API. Component1 and Component2 are subscribed to observable returned by Service:

import { Injectable } from "@angular/core";
import { Http } from "@angular/http";

import 'rxjs/add/operator/map';

@Injectable()

export class GroupService{
    constructor(private _http: Http){}

    getGroups(){
        return this._http.get('/assets/groups.json')
            .map((res) => {
                return res.json();
            });
    }
}

Component1 and Component2 are basically the same in terms of functionality. Here's one of them:

import { Component } from '@angular/core';
import { router } from '../../app.router';
import { GroupService } from '../../services/group.service';
import { Group } from '../../interfaces/group.interface';

@Component({
    selector: 'app-sidebar',
    templateUrl: './sidebar.component.html',
    styleUrls: ['./sidebar.component.scss'],
})

export class SidebarComponent
{
    router;
    groups: Group[];

    constructor(private _groupService: GroupService){
        this.router = router;
        this.getGroups();
    }

    getGroups(){
        this._groupService.getGroups()
            .subscribe((groups) => {
                this.groups = groups;
            });
    }
}

Upvotes: 2

Views: 1584

Answers (2)

seidme
seidme

Reputation: 13058

Using BehaviorSubject as a delegate is probably the most convenient option for your use case. Register the BehaviorSubject in the service and subscribe to it in all components that need to be updated when any of those components requests new data.

This is how the service should look like:

@Injectable()
export class GroupService {

    groupsSource: BehaviorSubject<any> = new BehaviorSubject(null);

    constructor(private _http: Http) { }

    getGroups(): void {
        this._http.get('/assets/groups.json')
            .map((res) => res.json())
            .subscribe((groups) => this.groupsSource.next(groups)); // Update all components subscribed to the BehaviorSubjet
    }
}

Component 1, requests new data:

export class Component1 implements OnInit {

    groups: any;

    constructor(private _groupService: GroupService) { }

    ngOnInit() {
        this._groupService.groupsSource.subscribe((groups) => {
            this.groups = groups;
        });

        this._groupService.getGroups();
    }
}

Component 2, gets updated automatically:

export class Component2 implements OnInit {

    groups: any;

    constructor(private _groupService: GroupService) { }

    ngOnInit() {
        this._groupService.groupsSource.subscribe((groups) => {
            this.groups = groups;
        });
    }
}

With the BehaviorSubject you can get the most recent value it holds without even subscribing to it:

export class Component3 implements OnInit{

    groups: any;

    constructor(private _groupService: GroupService) { }

    ngOnInit() {
        this.groups = this._groupService.groupsSource.getValue();
    }
}

Upvotes: 3

Brother Woodrow
Brother Woodrow

Reputation: 6372

A slightly different approach from seidme's, that I would personally prefer:

Set up a data stream in GroupService and use that in your components.

Service:

import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';

import 'rxjs/add/operator/map';

@Injectable()
export class GroupService {
    private groupSource = new BehaviorSubject<any>({});
    public group$: Observable = this.groupSource.asObservable();

    constructor(private _http: Http) {}

    getGroups() {
        this._http.get('/assets/groups.json')
            .map((res) => {
                return res.json();
            })
            .subscribe((groups) => this.groupSource.next(groups));
    }
}

Then in your components, just do this:

import { Component, OnInit } from '@angular/core';
import { router } from '../../app.router';
import { GroupService } from '../../services/group.service';
import { Group } from '../../interfaces/group.interface';
import { Observable } from 'rxjs/Observable';

@Component({
    selector: 'app-sidebar',
    templateUrl: './sidebar.component.html',
    styleUrls: ['./sidebar.component.scss'],
})

export class SidebarComponent implements OnInit {
    router;
    groups: Observable;

    constructor(private _groupService: GroupService){
        this.router = router;
    }

    ngOnInit {
        this.groups = this._groupService.group$;
    }
}

Then in your template, use the async pipe: {{ groups | async }}. It will take care of subscribing to (and disposing of) the observable for you.

Now whenever you call GroupService.getGroups() from any component, all the other ones will update automatically.

Also, using OnInit is slightly preferable to doing stuff in the constructor, see Difference between Constructor and ngOnInit.

Upvotes: 5

Related Questions