【问题标题】:Where to put service providers in angular 2 hierarchy so that components can talk to each other using the same instance of service?在哪里将服务提供者放在 Angular 2 层次结构中,以便组件可以使用相同的服务实例相互通信?
【发布时间】:2016-08-12 20:05:31
【问题描述】:

相关问题:

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

我有一个具有 setCurrentPlaylists 函数的 PagesService,该函数将从其他组件触发,它将接收播放列表类型的值,并将控制台记录该值,使用下一个函数传递给其他组件(我打算)。

我的页面服务的整个代码是:

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

我的页面组件代码是:

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

}

当我触发 setCurrentPlaylists 函数时,播放列表没有传递给 pages 组件。我需要使用传递的值来更新页面组件的播放列表。

这是我触发 setCurrentPlaylsts 函数后的控制台输出。没有来自页面组件的消息。

欢迎提出任何建议!

我从这个组件调用 setCurrentPlaylists 函数

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

}

这是我的组件树:

【问题讨论】:

  • 你在一个大的 if 语句中有一个条件是 Moives 而不是 Movies。这可能是一个错字吗?考虑创建一个枚举来避免这种情况。
  • @HuseinRoncevic 感谢您的指出!是的,这是一个错字

标签: javascript angular reactive-programming rxjs angular2-injection


【解决方案1】:

我假设您的组件树如下:

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

问题

您应该只将您的服务放在顶级(父)组件provider。虽然所有组件仍然需要进行导入和构造函数。

将服务放在组件的提供者中将创建服务的新副本,并沿着组件树向下而不是向上共享。

在有问题的代码中,PagesComponent 作为树中最底层的子级,具有自己的provider 行,实际上是在启动自己的 PagesService 副本 PlaylistService。所以 PagesComponent 的每个实例基本上都只听自己的。它不会收到其他人的任何消息。

修复

@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>;

在哪里放置providers

假设以下组件树:

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

最简单的方法是把它放在A1providers,所有组件将共享同一个服务实例,并且能够相互发送消息。

如果你把它放在B1providers,那么只有B1、C1和C2可以互相交谈。

基于最新更新,项目的根组件是 AppComponent.ts。 providers 应该加进去。

【讨论】:

  • @johuSiu,您好,我已经更新了问题,感谢您的回答,我正在尝试看看是否能解决我的问题。
  • @XinruiMa 已更新。 AppComponent.ts 是你的根组件。
  • @JohuSiu,谢谢!问题解决了!学到了新知识,非常感谢。你从哪里学来的?你值得拥有 50+ 的声誉。
  • @XinruiMa 我向(1)angular.io/docs/ts/latest/quickstart.html学习。当我几个月前开始时,它是在 RC4 中。我基本上是手动完成教程。然后在网上大量阅读并完成一些项目。 (2) 前段时间我犯了和你一模一样的错误,我花了 3 天时间才发现它。
  • @XinruiMa 你应该看看RC5,这实际上不会发生,RC5将提供程序移到component.ts之外并进入module.ts。
【解决方案2】:

从你提供的代码中,我看不到这个方法是什么时候

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

被调用。因此,您的列表是空的。

这样做

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

只创建对 observable 的订阅。您需要从某个地方发布数据。

另外,最好把这段代码挪一下

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

ngOnInit()

【讨论】:

  • 更新了我的问题,我确实将数据推送到 setCurrentPlaylists 函数,我很困惑,在这种情况下,页面服务中的 currentPlaylists 既是可观察的又是观察者?
  • @XinruiMa 你在哪里调用setCurrentPlaylist?我认为 Toan Nguyen 是对的。
  • @HuseinRoncevic 我会更新我的问题来回答这个问题,谢谢。
猜你喜欢
  • 2014-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-26
  • 2020-04-04
  • 1970-01-01
  • 1970-01-01
  • 2017-03-22
相关资源
最近更新 更多