Angular 2 Компонент Коммуникации Sibling

У меня есть ListComponent. Когда элемент кликается в ListComponent, детали этого элемента должны отображаться в DetailComponent. Оба одновременно отображаются на экране, поэтому нет маршрутизации.

Как указать DetailComponent, какой элемент в ListComponent был нажат?

Я рассмотрел вопрос о выпуске события до родителя (AppComponent), и родительский элемент set selectedItem.id на DetailComponent с @Input. Или я мог бы использовать общую службу с наблюдаемыми подписками.


РЕДАКТИРОВАТЬ: Установка выбранного элемента с помощью события + @Ввод не запускает DetailComponent, хотя, если мне нужно будет выполнить дополнительный код. Поэтому я не уверен, что это приемлемое решение.


Но оба этих метода кажутся гораздо более сложными, чем способ Angular 1 делать то, что было либо через $rootScope. $broadcast или $scope. $parent. $broadcast.

Когда все в Angular 2 является компонентом, я удивляюсь, что там больше информации о связи компонентов.

Есть ли еще один или более простой способ сделать это?

Ответ 1

Обновлено до rc.4: При попытке получить данные, переданные между компонентами-братьями, в angular 2, самый простой способ (angular.rc.4) - использовать angular2 иерархическую инъекцию зависимостей и создать общую службу.

Здесь будет служба:

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

@Injectable()
export class SharedService {
    dataArray: string[] = [];

    insertData(data: string){
        this.dataArray.unshift(data);
    }
}

Теперь здесь будет компонент PARENT

import {Component} from '@angular/core';
import {SharedService} from './shared.service';
import {ChildComponent} from './child.component';
import {ChildSiblingComponent} from './child-sibling.component';
@Component({
    selector: 'parent-component',
    template: `
        <h1>Parent</h1>
        <div>
            <child-component></child-component>
            <child-sibling-component></child-sibling-component>
        </div>
    `,
    providers: [SharedService],
    directives: [ChildComponent, ChildSiblingComponent]
})
export class parentComponent{

} 

и его двух детей

ребенок 1

import {Component, OnInit} from '@angular/core';
import {SharedService} from './shared.service'

@Component({
    selector: 'child-component',
    template: `
        <h1>I am a child</h1>
        <div>
            <ul *ngFor="#data in data">
                <li>{{data}}</li>
            </ul>
        </div>
    `
})
export class ChildComponent implements OnInit{
    data: string[] = [];
    constructor(
        private _sharedService: SharedService) { }
    ngOnInit():any {
        this.data = this._sharedService.dataArray;
    }
}

ребенок 2 (это брат)

import {Component} from 'angular2/core';
import {SharedService} from './shared.service'

@Component({
    selector: 'child-sibling-component',
    template: `
        <h1>I am a child</h1>
        <input type="text" [(ngModel)]="data"/>
        <button (click)="addData()"></button>
    `
})
export class ChildSiblingComponent{
    data: string = 'Testing data';
    constructor(
        private _sharedService: SharedService){}
    addData(){
        this._sharedService.insertData(this.data);
        this.data = '';
    }
}

СЕЙЧАС: Что следует учитывать при использовании этого метода.

  • Включить только поставщика услуг для общей службы в компоненте PARENT и NOT для детей.
  • Вам все равно придется включать конструкторы и импортировать службу в дочерних
  • Ответ на этот вопрос был первоначально дан для ранней бета-версии angular 2. Все, что изменилось, это операторы импорта, так что это все, что вам нужно обновить, если вы случайно использовали исходную версию.

Ответ 2

В случае двух разных компонентов (не вложенных компонентов, parent\child\grandchild) я предлагаю вам следующее:

MissionService:

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

@Injectable()

export class MissionService {
  // Observable string sources
  private missionAnnouncedSource = new Subject<string>();
  private missionConfirmedSource = new Subject<string>();
  // Observable string streams
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();
  // Service message commands
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }
  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }

}

AstronautComponent:

import { Component, Input, OnDestroy } from '@angular/core';
import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs/Subscription';
@Component({
  selector: 'my-astronaut',
  template: '
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  '
})
export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;
  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }
  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }
  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}

Источник: Родитель и дети общаются через сервис

Ответ 3

Один из способов сделать это - использовать общий сервис.

Однако я считаю, что следующее решение намного проще, оно позволяет обмениваться данными между двумя братьями и сестрами (я проверял это только на Angular 5).

В шаблоне родительского компонента:

<!-- Assigns "AppSibling1Component" instance to variable "data" -->
<app-sibling1 #data></app-sibling1>
<!-- Passes the variable "data" to AppSibling2Component instance -->
<app-sibling2 [data]="data"></app-sibling2> 

приложение-sibling2.component.ts

import { AppSibling1Component } from '../app-sibling1/app-sibling1.component';
...

export class AppSibling2Component {
   ...
   @Input() data: AppSibling1Component;
   ...
}

Ответ 4

Здесь обсуждается

https://github.com/angular/angular.io/issues/2663

Ответ Alex J хорош, но он больше не работает с текущей версией Angular 4 по состоянию на июль 2017 года.

И эта плункерная ссылка продемонстрирует, как общаться между братьями и сестрами, используя общий сервис и наблюдаемый.

https://embed.plnkr.co/P8xCEwSKgcOg07pwDrlO/

Ответ 5

В определенных ситуациях директива может иметь смысл "соединять" компоненты. На самом деле соединяемые вещи даже не должны быть полными компонентами, а иногда они более легкие и, на самом деле, проще, если это не так.

Например, у меня есть компонент Youtube Player (обёртка YouTube API), и я хотел использовать для него несколько кнопок контроллера. Единственная причина, по которой кнопки не являются частью моего основного компонента, заключается в том, что они расположены в другом месте DOM.

В этом случае это действительно просто "дополнительный" компонент, который когда-либо будет полезен только с "родительским" компонентом. Я говорю "родитель", но в DOM это брат, так что назовите это как хотите.

Как я уже сказал, он даже не должен быть полным компонентом, в моем случае это просто <button> (но это может быть компонент).

@Directive({
    selector: '[ytPlayerPlayButton]'
})
export class YoutubePlayerPlayButtonDirective {

    _player: YoutubePlayerComponent; 

    @Input('ytPlayerVideo')
    private set player(value: YoutubePlayerComponent) {
       this._player = value;    
    }

    @HostListener('click') click() {
        this._player.play();
    }

   constructor(private elementRef: ElementRef) {
       // the button itself
   }
}

В HTML для ProductPage.component, где youtube-player - это, очевидно, мой компонент, который оборачивает API Youtube.

<youtube-player #technologyVideo videoId='NuU74nesR5A'></youtube-player>

... lots more DOM ...

<button class="play-button"        
        ytPlayerPlayButton
        [ytPlayerVideo]="technologyVideo">Play</button>

Директива подключает все для меня, и мне не нужно объявлять событие (click) в HTML.

Таким образом, директива может легко подключиться к видеоплееру без использования ProductPage в качестве посредника.

Это первый раз, когда я на самом деле сделал это, так что пока не уверен, насколько это масштабируемо для гораздо более сложных ситуаций. За это, хотя я счастлив, и это оставляет мой HTML простым и обязанности всего различного.

Ответ 6

Вам необходимо настроить отношения родитель-потомок между вашими компонентами. Проблема в том, что вы можете просто вставлять дочерние компоненты в конструктор родительского компонента и хранить его в локальной переменной. Вместо этого вы должны объявить дочерние компоненты в своем родительском компоненте с помощью декларатора свойств @ViewChild. Вот как выглядит ваш родительский компонент:

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ListComponent } from './list.component';
import { DetailComponent } from './detail.component';

@Component({
  selector: 'app-component',
  template: '<list-component></list-component><detail-component></detail-component>',
  directives: [ListComponent, DetailComponent]
})
class AppComponent implements AfterViewInit {
  @ViewChild(ListComponent) listComponent:ListComponent;
  @ViewChild(DetailComponent) detailComponent: DetailComponent;

  ngAfterViewInit() {
    // afther this point the children are set, so you can use them
    this.detailComponent.doSomething();
  }
}

https://angular.io/docs/ts/latest/api/core/index/ViewChild-var.html

https://angular.io/docs/ts/latest/cookbook/component-communication.html#parent-to-view-child

Остерегайтесь, дочерний компонент не будет доступен в конструкторе родительского компонента сразу после вызова ngAfterViewInit hook. Чтобы поймать этот крючок, просто реализуйте интерфейс AfterViewInit в родительском классе таким же образом, как и с OnInit.

Но есть и другие деклараторы свойств, как описано в этом блоге: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/

Ответ 7

Поведенческие предметы. Я написал блог об этом.

import { BehaviorSubject } from 'rxjs/BehaviorSubject';
private noId = new BehaviorSubject<number>(0); 
  defaultId = this.noId.asObservable();

newId(urlId) {
 this.noId.next(urlId); 
 }

В этом примере я объявляю субъектом noid-поведение типа номер. И это наблюдаемое. И если "что-то случилось", это изменится с помощью функции new() {}.

Таким образом, в одноуровневых компонентах один вызовет функцию, чтобы внести изменение, а другой будет зависеть от этого изменения, или наоборот.

Например, я получаю идентификатор из URL и обновляю noid из субъекта поведения.

public getId () {
  const id = +this.route.snapshot.paramMap.get('id'); 
  return id; 
}

ngOnInit(): void { 
 const id = +this.getId ();
 this.taskService.newId(id) 
}

А с другой стороны, я могу спросить, является ли этот идентификатор "тем, что я хочу", и сделать выбор после этого, в моем случае, если я хочу удалить задачу, а эта задача является текущей ссылкой, она должна перенаправить меня к дому:

delete(task: Task): void { 
  //we save the id , cuz after the delete function, we  gonna lose it 
  const oldId = task.id; 
  this.taskService.deleteTask(task) 
      .subscribe(task => { //we call the defaultId function from task.service.
        this.taskService.defaultId //here we are subscribed to the urlId, which give us the id from the view task 
                 .subscribe(urlId => {
            this.urlId = urlId ;
                  if (oldId == urlId ) { 
                // Location.call('/home'); 
                this.router.navigate(['/home']); 
              } 
          }) 
    }) 
}

Ответ 8

Вот простое практическое объяснение: просто объяснено здесь

В call.service.ts

import { Observable } from 'rxjs';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class CallService {
 private subject = new Subject<any>();

 sendClickCall(message: string) {
    this.subject.next({ text: message });
 }

 getClickCall(): Observable<any> {
    return this.subject.asObservable();
 }
}

Компонент, из которого вы хотите вызвать наблюдаемый, чтобы сообщить другому компоненту, что кнопка нажата

import { CallService } from "../../../services/call.service";

export class MarketplaceComponent implements OnInit, OnDestroy {
  constructor(public Util: CallService) {

  }

  buttonClickedToCallObservable() {
   this.Util.sendClickCall('Sending message to another comp that button is clicked');
  }
}

Компонент, в котором вы хотите выполнить действие при нажатии кнопки на другом компоненте

import { Subscription } from 'rxjs/Subscription';
import { CallService } from "../../../services/call.service";


ngOnInit() {

 this.subscription = this.Util.getClickCall().subscribe(message => {

 this.message = message;

 console.log('---button clicked at another component---');

 //call you action which need to execute in this component on button clicked

 });

}

import { Subscription } from 'rxjs/Subscription';
import { CallService } from "../../../services/call.service";


ngOnInit() {

 this.subscription = this.Util.getClickCall().subscribe(message => {

 this.message = message;

 console.log('---button clicked at another component---');

 //call you action which need to execute in this component on button clicked

});

}

Мое понимание ясно о компонентах связи, прочитав это: http://musttoknow.com/angular-4-angular-5-communicate-two-components-using-observable-subject/

Ответ 9

Это не то, что вы точно хотите, но наверняка поможет вам

Я удивлен, что там больше информации о связи компонентов <= > рассмотрите этот учебник angualr2

Для общения с родственными компонентами я предлагаю пойти с sharedService. Однако есть и другие варианты.

import {Component,bind} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {HTTP_PROVIDERS} from 'angular2/http';
import {NameService} from 'src/nameService';


import {TheContent} from 'src/content';
import {Navbar} from 'src/nav';


@Component({
  selector: 'app',
  directives: [TheContent,Navbar],
  providers: [NameService],
  template: '<navbar></navbar><thecontent></thecontent>'
})


export class App {
  constructor() {
    console.log('App started');
  }
}

bootstrap(App,[]);

Подробнее см. ссылку наверху.

Изменить: Это очень маленькая демонстрация. Вы уже упоминали, что вы уже пробовали с помощью sharedService. Поэтому, пожалуйста, рассмотрите этот учебник angualr2 для получения дополнительной информации.

Ответ 10

Общий сервис является хорошим решением для этой проблемы. Если вы также хотите сохранить некоторую информацию об активности, вы можете добавить Shared Service в список поставщиков основных модулей (app.module).

@NgModule({
    imports: [
        ...
    ],
    bootstrap: [
        AppComponent
    ],
    declarations: [
        AppComponent,
    ],
    providers: [
        SharedService,
        ...
    ]
});

Тогда вы можете напрямую предоставить его своим компонентам,

constructor(private sharedService: SharedService)

С Shared Service вы можете использовать функции или создать тему для обновления нескольких мест одновременно.

@Injectable()
export class FolderTagService {
    public clickedItemInformation: Subject<string> = new Subject(); 
}

В вашем компоненте списка вы можете опубликовать информацию о выбранном элементе,

this.sharedService.clikedItemInformation.next("something");

и затем вы можете получить эту информацию в вашем компоненте детали:

this.sharedService.clikedItemInformation.subscribe((information) => {
    // do something
});

Очевидно, что данные, которые разделяет список компонентов, могут быть любыми. Надеюсь это поможет.

Ответ 11

Я передавал методы setter от родителя к одному из его дочерних элементов через привязку, вызывая этот метод с данными из дочернего компонента, что означает, что родительский компонент обновлен и затем может обновить свой второй дочерний компонент с помощью новые данные. Это требует привязки 'this' или использования функции стрелки.

Это имеет то преимущество, что дети не так связаны друг с другом, поскольку они не нуждаются в конкретной общей службе.

Я не совсем уверен, что это лучшая практика, было бы интересно услышать мнения других по этому поводу.