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;
        });
    }

}
Community
  • 1
  • 1
bmd
  • 1,231
  • 3
  • 15
  • 23

3 Answers3

2

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!

Fredrik Lundin
  • 8,006
  • 1
  • 29
  • 35
  • Brilliant. Thanks, Fredrik! The RxJS concepts are still new to me so I had a feeling that is where I would have been a little off track. The only other thing I needed to change was in the ReleaseDetailComponent and ngOnInit hook --> this.release = this._releaseService.getRelease().subscribe(release=>{ return this.release = release; }); – bmd Feb 16 '17 at 13:35
  • Happy to help! :) – Fredrik Lundin Feb 16 '17 at 13:41
1

Consider using BehaviorSubject as a delegate. BehaviorSubject upon a subscription returns the last value of the subject, therefore the component loaded postponely will always be updated with the recent value:

@Injectable
export class ReleaseService {
    releaseSource: BehaviorSubject = new BehaviorSubject(null);

   // No difference when it comes to subscribing to the BehaviorSubject
}

Basic example of BehaviorSubject in action: Angular 2 Interaction between components using the service

Community
  • 1
  • 1
seidme
  • 12,543
  • 5
  • 36
  • 40
0

I think you are mixing two concepts here. There are two ways to do it:

  1. In the release-detail Component you declare the release with @Input and you pass it from the release-list. The you will have it at your disposal as variable in the component. No subject, no subscribe.
  2. You use a service as above, but you already load the details. Like embed it in the release-list html, but with *ngIf set to false, not showing it. The when you put the selected release in your subject and it will kick off, then you can set the *ngIf to true, showing the release-detail part.
Zolcsi
  • 2,026
  • 1
  • 13
  • 13