Related question:

Observable do not receive the next value in angular2

No provider for service error in angular2, why do I need to inject it in it's parent component?

Using observable talk to other component in angular2, not receiving coming value

I have a PagesService that has a setCurrentPlaylists function, this function will be triggered from other component, it will receive an value of Playlists type, and will console log this value, using the next function pass to other component( I intent to).

My entire code for pages service is:

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

import { ApiService } from '../../apiService/api.service';
import { Platform } from '../../platforms/shared/platform.model';
import { Page } from './page.model';
import { Playlists } from '../shared/playlists.model';
import { Subject, BehaviorSubject } from 'rxjs/Rx';


@Injectable()
export class PagesService {

  private currentPlaylists: Subject<Playlists> = new  BehaviorSubject<Playlists>(new Playlists());


  constructor(private service: ApiService) {
    this.currentPlaylists.subscribe((v) => console.log(v, 'subscriber from pages service is printing out the incoming value'));
  }

  getPages(platform: Platform) {
    return this.service.getPages(platform.value);
  }

  setCurrentPage(page: Page) {
    this.service.setCurrentPage(page.pageId);
  }

  getCurrentPage():string {
    return this.service.getCurrentPage();
  }

  getCurrentPlaylists() {
    return this.currentPlaylists;
  }

  setCurrentPlaylists(playlists: Playlists) {
    console.log("Pages Service receive an value of playlists:", playlists);
    this.currentPlaylists.next(playlists);
  }
}

My code for page component is:

import { Component, OnInit, Input, Output, OnChanges, EventEmitter, Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';

import { Platform } from '../platforms/shared/platform.model';
import { Page } from './shared/page.model';
import { Playlists } from './shared/playlists.model';
import { PagesService } from './shared/pages.service';
import { PlaylistService } from '../playlist/shared/playlist.service';
import { Subject,BehaviorSubject } from 'rxjs/Rx';


@Component({
  selector: 'pages',
  styleUrls: ['app/pages/pages.css'],
  templateUrl: 'app/pages/pages.html',
  providers: [PagesService, PlaylistService]
})

export class PagesComponent {

  @Input() platform: Platform;

  @Output() onPlaylistsChange: EventEmitter<Playlists>;

  currentPageName: string;

  currentPage: Page;

  pages: Array<Page>;

  playlists: Playlists;

  constructor(private pageServer: PagesService, private playlistService: PlaylistService) {
    this.pages = [];
    this.currentPage = new Page();
    this.pageServer.setCurrentPage(this.currentPage);
    this.playlists = new Playlists();
    this.onPlaylistsChange = new EventEmitter<Playlists>();
  }

  ngOnInit() {
    this.pageServer.getCurrentPlaylists().subscribe((playlists) => {
      console.log('subscriber in pages component is printing out the incoming value', playlists);
      this.playlists = playlists;
    }, error => {
      console.log(error);
    });
  }

  getPages(platform: Platform): void {
    this.pageServer.getPages(platform)
      .subscribe(
      res => {
        if (res.pages.length > 0) {
          this.pages = [];
          for (let page of res.pages) {
            if (page.pageName !== "Shows" && page.pageName !== "All Shows" && page.pageName !== "Moives" && page.pageName !== "All Movies") {
              this.pages.push(page);
            }
          }
          this.currentPage = this.pages[0];
          this.pageServer.setCurrentPage(this.currentPage);
          this.currentPageName = this.pages[0].pageName;
          this.getPlaylist(this.currentPage, this.platform);
        } else {
          this.pages = [];
          this.currentPage = new Page();
          this.pageServer.setCurrentPage(this.currentPage);
          this.playlists = new Playlists();
          this.onPlaylistsChange.emit(this.playlists);
        }
      },
      error => console.log(error)
      );
  }

  getPlaylist(page: Page, platform: Platform): void {
    this.currentPage = page;
    this.pageServer.setCurrentPage(this.currentPage);
    this.playlistService.getPlaylist(page, platform)
      .subscribe(
      res => {
        if (res.hasOwnProperty('pages') && res.pages.length > 0) {
          if (res.pages[0].hasOwnProperty('bodyPlaylists') && res.pages[0].hasOwnProperty('headerPlaylists')) {
            this.playlists.bodyPlaylists = res.pages[0].bodyPlaylists || [];
            this.playlists.headerPlaylists = res.pages[0].headerPlaylists || [];
          } else {
            this.playlists.bodyPlaylists = [];
            this.playlists.headerPlaylists = [];
            this.playlists.wholePlaylists = res.pages[0].playlists || [];
          }
          this.onPlaylistsChange.emit(this.playlists);
        } else {
          this.playlists = new Playlists();
          this.onPlaylistsChange.emit(this.playlists);
        }
      },
      error => console.error(error)
      );
  }

  ngOnChanges() {
    // Get all Pages when the platform is set actual value;
    if (this.platform.hasOwnProperty('value')) {
      this.getPages(this.platform);
    }
  }

}

When I trigger the setCurrentPlaylists function, the playlists didn't passed to pages component. I need to use that passed value to update pages component's playlists.

This is the console output after I trigger the setCurrentPlaylsts function. No message from pages components. enter image description here

Any suggestions are appreciated!

I call setCurrentPlaylists function from this component

/// <reference path="../../../typings/moment/moment.d.ts" />
import moment from 'moment';

import { Component, ViewChild, ElementRef, Input, Output, EventEmitter } from '@angular/core';
import { CORE_DIRECTIVES } from '@angular/common';
import { Http, Response } from '@angular/http';
import { MODAL_DIRECTVES, BS_VIEW_PROVIDERS } from 'ng2-bootstrap/ng2-bootstrap';
import {
  FORM_DIRECTIVES,
  REACTIVE_FORM_DIRECTIVES,
  FormBuilder,
  FormGroup,
  FormControl,
  Validators
} from '@angular/forms';

import { PagesService } from '../../pages/shared/pages.service';
import { ApiService } from '../../apiService/api.service';

@Component({
  selector: 'assign-playlist-modal',
  providers: [PagesService],
  exportAs: 'assignModal',
  directives: [MODAL_DIRECTVES, CORE_DIRECTIVES, FORM_DIRECTIVES, REACTIVE_FORM_DIRECTIVES ],
  viewProviders: [BS_VIEW_PROVIDERS],
  styleUrls: ['app/channel/shared/assignPlaylist.css'],
  templateUrl: 'app/channel/modals/assignPlaylistModal.html'
})

export class AssignPlaylistModalComponent {

  @ViewChild('assignPlaylistModal') modal: any;

  private addPlaylistForm: FormGroup;

  private playlistType: string;

  private currentPage: string;

  private editDate: string;

  constructor(private apiService: ApiService, private pagesService: PagesService, fb: FormBuilder) {
    this.currentPage = '';
    this.editDate = this.apiService.getDate();
    this.addPlaylistForm = fb.group({
      'longPlaylistName': ['', Validators.required],
      'shortPlaylistName': ['', Validators.required],
      'startOn': ['', Validators.compose([
        Validators.required, this.validTimeFormat
      ])],
      'expireOn': ['', Validators.compose([
        Validators.required, this.validTimeFormat
      ])],
      'isExpire': ['']
    });

    this.addPlaylistForm.controls['startOn'].valueChanges.subscribe((value: string) => {
      if (moment(value, 'YYYY-MM-DDThh:mm').isValid()) {
        if (this.playlistType == 'dynamic') {
          this.apiService.setGlobalStartTime(moment(value).format("YYYYMMDDHHmm"));
        }
      }
    });

    this.addPlaylistForm.controls['expireOn'].valueChanges.subscribe((value: string) => {
      if (moment(value, 'YYYY-MM-DDThh:mm').isValid()) {
        if (this.playlistType == 'dynamic') {
          this.apiService.setGlobalEndTime(moment(value).format("YYYYMMDDHHmm"));
        }
      }
    });
  }

  showModal(type: string) {
    this.playlistType = type;
    this.currentPage = this.apiService.getCurrentPage();
    this.modal.show();
  }

  validTimeFormat(control: FormControl): { [s: string]: boolean} {
    if (!moment(control.value, 'YYYY-MM-DDThh:mm').isValid()) {
      return { invalidTime: true};
    }
  }

  setCloseStyle() {
    let styles = {
      'color': 'white',
      'opacity': 1
    }
    return styles;
  }

  createNewPlaylist(stDate: string, etDate: string, playlistTitle: string, shortTitle: string, callback?: any):any {
    this.apiService.createNewPlaylist(stDate, etDate, playlistTitle, shortTitle)
    .subscribe(
      data => {
          let playlistId = data[0].id;
          this.apiService.addPlaylistToPage(playlistId, stDate, etDate, this.apiService.getGlobalRegion(), callback)
          .subscribe(
            data => {
              if (this.apiService.g_platform == 'DESKTOP') {
                this.apiService.getPlaylist(this.apiService.getCurrentPage(), 'true' )
                .subscribe(
                  res => {
                    if (res.hasOwnProperty('pages') && res.pages.length > 0) {
                      if (res.pages[0].hasOwnProperty('bodyPlaylists') && res.pages[0].hasOwnProperty('headerPlaylists')) {
                        this.apiService.getCurrentPlaylists().bodyPlaylists = res.pages[0].bodyPlaylists || [];
                        this.apiService.getCurrentPlaylists().headerPlaylists = res.pages[0].headerPlaylists || [];
                        console.log('assign playlist component is calling the pages service setCurrentPlaylists function.');
                        this.pagesService.setCurrentPlaylists(this.apiService.getCurrentPlaylists());
                      } else {
                        this.apiService.getCurrentPlaylists().bodyPlaylists = [];
                        this.apiService.getCurrentPlaylists().headerPlaylists = [];
                        this.apiService.getCurrentPlaylists().wholePlaylists = res.pages[0].playlists || [];
                        console.log('assign playlist component is calling the pages service setCurrentPlaylists function.');
                        this.pagesService.setCurrentPlaylists(this.apiService.getCurrentPlaylists());
                      }
                    }
                  }
                );
            } else {
              this.apiService.getPlaylist(this.apiService.getCurrentPage(), 'false' )
              .subscribe(
                res => {
                  if (res.hasOwnProperty('pages') && res.pages.length > 0) {
                      this.apiService.getCurrentPlaylists().bodyPlaylists = [];
                      this.apiService.getCurrentPlaylists().headerPlaylists = [];
                      this.apiService.getCurrentPlaylists().wholePlaylists = res.pages[0].playlists || [];
                      console.log('assign playlist component is calling the pages service setCurrentPlaylists function.');
                      this.pagesService.setCurrentPlaylists(this.apiService.getCurrentPlaylists());
                  }
                }
              );
            }
            }
          );
      },
      error => console.log(error)
    );

  }

  onSubmit(form: FormGroup) {


    // get start time, the format from input will be like 2016-06-07T00:05
    let startTime = moment(form.value.startOn).format("YYYYMMDDHHmm");
    let expireTime = moment(form.value.expireOn).format("YYYYMMDDHHmm");
    let playlistTitle = form.value.longPlaylistName;
    let shortTitle = form.value.shortPlaylistName;
    if (this.playlistType == 'smart' || this.playlistType == 'new') {
      this.createNewPlaylist(startTime, expireTime, playlistTitle, shortTitle);
    }
  }

}

This is my component tree: enter image description here

2

There are 2 best solutions below

8
On BEST ANSWER

I am assuming your components tree is as follow:

AssignPlaylistModalComponent (Parent or higher level than PagesComponent in the tree)
  PagesComponent (lowest level child as it does not import any directive)

Issue

You should only put your service in the top level (parent) components provider. Though all components still need to do the import and constructor.

Putting the service in a component's provider will create a new copy of the service and share along the component tree downward, not upward.

In the code in question, PagesComponent, as the lowest level child in the tree, with its own provider line, is actually initiating its own copy of PagesService, PlaylistService. So each instance of PagesComponent is basically listening to itself only. It won't receive any messages from others.

Fix

@Component({
  selector: 'pages',
  styleUrls: ['app/pages/pages.css'],
  templateUrl: 'app/pages/pages.html',
  providers: [PagesService, PlaylistService] // <--- Delete this line
})

export class PagesComponent {

  @Input() platform: Platform;

  @Output() onPlaylistsChange: EventEmitter<Playlists>;

Where to put providers

Assume following component tree:

   Component A1 (root component)
     Component B1
       Component C1
       Component C2
     Component B2
       Component C3
       Component C4

The easiest way is to put it in A1 providers, all components will be sharing the same service instance, and able to message each other.

If you put it in B1 providers, then only B1, C1 and C2 can talk to each other.

Base on lastest update, the root component of the project is AppComponent.ts. providers should be added in it.

3
On

From the code you provided, I cannot see when this method

  setCurrentPlaylists(playlists: Playlists) {
    console.log(playlists, 'i am here');
    this.currentPlaylists.next(playlists);
  }

is called. Therefore, your list is empty.

Doing this

this.pageServer.getCurrentPlaylists().subscribe((playlists) => {
      console.log(playlists, 'new playlists coming');
      this.playlists = playlists;
    }, error => {
      console.log(error);
    });

only creates a subscription to the observable. You need to publish data from somewhere.

In addition, it'd better to move this code

this.pageServer.getCurrentPlaylists().subscribe((playlists) => {
      console.log(playlists, 'new playlists coming');
      this.playlists = playlists;
    }, error => {
      console.log(error);
    });

to ngOnInit()