Search code examples
angularangular2-services

Sending data between components using a service in Angular 2


I'm trying to send data between two components using a service. However, I'm having trouble with the data populating when the 'child' component first loads.

When you select a 'release' from the unordered list on the release-list.component, it will load the release-detail.component through a router-outlet. The release-detail.component should show the release artist.

It works if you select a release and then select a release for a second time. But I'm having to 'load' the release-detail.component twice before the data is populated. I feel I'm close but I must be missing something. Any help would be greatly appreciated.

Thanks

I've referred to both a previous question and the Angular documentation

release.service.ts

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

import { MockReleaseList, ReleaseInterface } from './mock-releases';

@Injectable()

export class ReleaseService {
    private releaseSubject$ = new Subject();

    getReleases(): ReleaseInterface[] {
        return MockReleaseList;
    }

    getRelease() {
        return this.releaseSubject$;
    }

    updateRelease(release: {artist: string, title: string, category: string}[]) {
        // console.log(release);
        this.releaseSubject$.next(release);
    }

}

release-list.component.ts

import { Component, OnInit } from '@angular/core';

import { Router } from '@angular/router';
import { Location } from '@angular/common';

import { ReleaseService } from '../../../shared/services/release.service';
import { MockReleaseList, ReleaseInterface } from '../../../shared/services/mock-releases';

@Component({
  selector: 'ostgut-releases',
  template: `
    <h3>Label List</h3>
    <ul>
        <li *ngFor="let release of releases" (click)="onSelect(release)">{{ release.artist }}</li>
    </ul>
    <router-outlet></router-outlet>
  `,
})
export class ReleaseListComponent implements OnInit { 
    private releases: ReleaseInterface[];

    constructor (
        private _router: Router,
        private _releaseService: ReleaseService
    ) {}

    ngOnInit(): ReleaseInterface[] {
        return this.releases = this._releaseService.getReleases();
    }

    onSelect(release: any): void {
        this._releaseService.updateRelease(release);
        this._router.navigate(['/release-list/release-detail', release.category]);
    }

}

release-detail.component.ts

import { Component, OnInit, Input } from '@angular/core';

import { Router, ActivatedRoute, Params } from '@angular/router';

import { ReleaseService } from '../../../../shared/services/release.service';
import { ReleaseInterface } from '../../../../shared/services/mock-releases';

@Component({
  selector: 'ostgut-release-detail',
  template: `
    <h3>Release Detail</h3>
    <p>{{ release.artist }}</p>
  `,
})
export class ReleaseDetailComponent implements OnInit { 
    private routeParam: string;
    private routeParamSub: any;
    private release: any;

    constructor(
        private _activatedRoute: ActivatedRoute,
        private _router: Router,
        private _releaseService: ReleaseService
    ) {}

    ngOnInit(): void {
        this.release = this._releaseService.getRelease().subscribe(release=>{
            return this.release = release;
        });
    }

}

Solution

  • I think the problem could be that your Subject publishes its value before you are subscribing to it inside your ReleaseDetailComponent. When you do it the second time, a new Release is published, and the ReleaseDetailComponent is already created - meaning it will work.

    To fix this, you can try to use a BehaviorSubject instead of a regular Subject in your service. The BehaviorSubject will give you the latest published value even if you subscribe to it after the last value was published. It also expects a default value though, so you might want to add some null-checking in your ReleaseDetailComponent template.

    An implementation could look something like this:

    import { Injectable } from '@angular/core';
    import { BehaviorSubject } from 'rxjs/BehaviorSubject'; // <-- note the import!
    
    import { MockReleaseList, ReleaseInterface } from './mock-releases';
    
    @Injectable()
    
    export class ReleaseService {
        private releaseSubject$ = new BehaviorSubject(undefined);
    
       // The rest is the same
    
    }
    

    And in your template, att null checking, as the default value we set to the BehaviorSubject is undefined. Of course, you can set the default value (the values passed into the constructor of the BehaviorSubject) to an empty Release or something else if you wish.

    template: `
        <h3>Release Detail</h3>
        <p>{{ release?.artist }}</p> <!-- Note the '?' -->
      `,
    

    Here are the docs if you want to learn more about the different types of Subjects:

    I hope this helps!