Angular 2 형제 구성 요소 통신


118

ListComponent가 있습니다. ListComponent에서 항목을 클릭하면 해당 항목의 세부 정보가 DetailComponent에 표시되어야합니다. 둘 다 동시에 화면에 표시되므로 라우팅이 필요하지 않습니다.

ListComponent에서 클릭 한 항목을 DetailComponent에게 어떻게 알립니 까?

부모 (AppComponent)까지 이벤트를 생성하는 것을 고려했으며 부모가 @Input을 사용하여 DetailComponent에 selectedItem.id를 설정하도록했습니다. 또는 관찰 가능한 구독으로 공유 서비스를 사용할 수 있습니다.


편집 : 이벤트 + @Input을 통해 선택한 항목을 설정하면 추가 코드를 실행해야 할 경우 DetailComponent가 트리거되지 않습니다. 그래서 이것이 허용 가능한 해결책인지 확신하지 못합니다.


그러나이 두 방법 모두 $ rootScope. $ broadcast 또는 $ scope. $ parent. $ broadcast를 통해 작업을 수행하는 Angular 1 방법보다 훨씬 복잡해 보입니다.

Angular 2의 모든 것이 구성 요소이기 때문에 구성 요소 통신에 대한 더 많은 정보가 없다는 것에 놀랐습니다.

이를 수행하는 또 다른 /보다 간단한 방법이 있습니까?


형제 데이터 공유 방법을 찾았습니까? 내가 관찰로 필요 ..
인류에게

답변:


65

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 = '';
    }
}

NOW :이 방법을 사용할 때주의해야 할 사항입니다.

  1. 하위가 아닌 PARENT 구성 요소의 공유 서비스에 대한 서비스 제공자 만 포함하십시오.
  2. 여전히 생성자를 포함하고 하위에 서비스를 가져와야합니다.
  3. 이 답변은 원래 초기 Angular 2 베타 버전에서 답변되었습니다. 그러나 변경된 것은 모두 import 문이므로 원래 버전을 우연히 사용한 경우 업데이트해야 할 전부입니다.

2
angular-rc1에도 여전히 유효합니까?
Sergio

4
나는 이것이 공유 서비스에서 무언가가 업데이트되었음을 ​​형제에게 알리는 것을 믿지 않습니다. child-component1이 child-component2가 응답해야하는 작업을 수행하는 경우이 메서드는이를 처리하지 않습니다. 나는 그 주위의 방법이 관찰 가능하다고 믿습니까?
dennis.sheppard

1
@Sufyan : 공급자 필드를 자식에 추가하면 Angular가 각각에 대해 새 개인 인스턴스를 생성한다고 추측 할 것입니다. 추가하지 않으면 부모의 "단일"인스턴스를 사용합니다.
Ralph

1
이처럼 보인다는 최신 업데이트와 함께 더 이상 작동하지 않습니다
Sufyan 브르

3
이것은 구식입니다. directives컴포넌트에서 더 이상 선언되지 않습니다.
Nate Gardner

26

두 개의 다른 구성 요소 (내포 된 구성 요소가 아닌 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);
  }

}

Astronaut 구성 요소 :

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

출처 : 부모와 자녀는 서비스를 통해 소통합니다.


2
이 답변에 몇 가지 용어를 추가 하시겠습니까? 나는 그것이 RxJS, Observable 패턴 등과 일치한다고 믿습니다. 완전히 확실하지는 않지만 이것에 대한 설명을 추가하면 (나와 같은) 사람들에게 유익 할 것입니다.
karns

13

이를 수행하는 한 가지 방법은 공유 서비스를 사용하는 것 입니다.

그러나 다음 솔루션이 훨씬 간단하다는 것을 알게되어 두 형제간에 데이터를 공유 할 수 있습니다 (나는 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> 

app-sibling2.component.ts

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

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

이것은 느슨한 결합과 구성 요소의 개념에 반대되지 않습니까?
Robin

누구든지 이것이 깨끗하거나 더러운 방법인지 알 수 있습니까? 한 방향으로 데이터를 공유하는 것이 훨씬 더 간단 해 보입니다. 예를 들어 치찰음 1에서 치찰음 2로만 데이터를 공유하지만 그 반대는 아닙니다
Sarah


7

지시문은 특정 상황에서 구성 요소를 '연결'하는 데 의미가있을 수 있습니다. 실제로 연결되는 사물이 전체 구성 요소 일 필요도 없으며 때로는 더 가볍고 그렇지 않은 경우 실제로 더 간단합니다.

예를 들어 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 에서 Youtube API를 래핑하는 내 구성 요소는 분명히 ProductPage.component어디에 있습니까 youtube-player?

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

... lots more DOM ...

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

지시문은 나를 위해 모든 것을 연결하고 HTML에서 (클릭) 이벤트를 선언 할 필요가 없습니다.

따라서 지시문은 ProductPage중재자 로 참여하지 않고도 비디오 플레이어에 멋지게 연결할 수 있습니다 .

실제로 이것을 한 것은 이번이 처음이므로 훨씬 더 복잡한 상황에서 얼마나 확장 가능한지 아직 확실하지 않습니다. 이를 위해 나는 행복하지만 HTML은 단순하고 모든 것에 대한 책임은 구별됩니다.


이해해야 할 가장 중요한 각도 개념 중 하나는 구성 요소가 템플릿이있는 지시문이라는 것입니다. 이것이 의미하는 바를 정말로 이해하면 지시어는 그렇게 무섭지 않습니다. 그리고 행동을 첨부하기 위해 어떤 요소에도 적용 할 수 있다는 것을 알게 될 것입니다.
Simon_Weaver

나는 이것을 시도했지만에 상응하는 중복 식별자 오류가 발생합니다 player. 플레이어에 대한 첫 번째 언급을 생략하면 rangeError가 발생합니다. 나는 이것이 어떻게 작동하는지에 대해 난처합니다.
Katharine Osborne 2018

@KatharineOsborne 실제 코드 _player에서 플레이어를 나타내는 개인 필드에 사용 하는 것처럼 보이 므로 정확하게 복사하면 오류가 발생합니다. 업데이트됩니다. 죄송합니다!
Simon_Weaver

4

여기에 간단한 실제적인 설명은 다음과 같습니다 간단히 설명 여기

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

버튼이 클릭되었음을 다른 컴포넌트에 알리기 위해 observable을 호출하려는 컴포넌트

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/ 를 읽고 구성 요소 통신에 대한 이해가 명확합니다 .


안녕하세요 간단한 솔루션에 감사드립니다> 잘 작동하는 stackblitz에서 시도했습니다. 하지만 내 응용 프로그램에는 지연로드 된 경로 (제공 : 'root'를 사용함)와 HTTP 호출을 설정하고 가져옵니다. HTTP 호출을 도와 주시겠습니까? 많이 시도했지만 작동하지 않음 :
Kshri

4

공유 서비스는이 문제에 대한 좋은 해결책입니다. 일부 활동 정보도 저장하려면 기본 모듈 (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
});

구성 요소 공유를 나열하는 데이터는 무엇이든 될 수 있습니다. 도움이 되었기를 바랍니다.


이것은 공유 서비스의이 개념에 대한 가장 간단한 (간결한) 예이며, 허용되는 답변이 없기 때문에 가시성을 높이려면 실제로 upvoted해야합니다.
iGanja

3

구성 요소 간의 상위-하위 관계를 설정해야합니다. 문제는 단순히 부모 구성 요소의 생성자에 자식 구성 요소를 삽입하고 로컬 변수에 저장할 수 있다는 것입니다. 대신 @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수명주기 후크가 호출 된 직후에는 부모 구성 요소의 생성자에서 자식 구성 요소를 사용할 수 없습니다 . 이 후크를 잡으려면 AfterViewInit에서와 같은 방식으로 부모 클래스에서 인터페이스를 구현하십시오 OnInit.

하지만이 블로그 노트에 설명 된 다른 속성 선언자가 있습니다. http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/


2

행동 주제. 그것에 대해 블로그 를 썼습니다 .

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

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

이 예에서는 숫자 유형의 noid 동작 주제를 선언합니다. 또한 관찰 가능합니다. 그리고 "무언가 발생"하면 new () {} 함수로 변경됩니다.

따라서 형제의 구성 요소에서 하나는 함수를 호출하여 변경하고 다른 하나는 해당 변경의 영향을 받거나 그 반대의 경우도 마찬가지입니다.

예를 들어 URL에서 ID를 가져오고 동작 주제에서 noid를 업데이트합니다.

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

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

그리고 다른 쪽에서는 그 ID가 "내가 원하는 것"인지 묻고 그 후에 선택할 수 있습니다. 제 경우에는 작업을 삭제하고 해당 작업이 현재 URL인지 여부를 선택하면됩니다. 집으로 :

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

1

이것은 당신이 정확히 원하는 것은 아니지만 확실히 당신을 도울 것입니다

컴포넌트 통신에 대한 더 많은 정보가 없다는 것에 놀랐습니다. <=> 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의이 튜토리얼을 고려 하십시오.


0

바인딩을 통해 부모에서 자식 중 하나로 setter 메서드를 전달하여 자식 구성 요소의 데이터로 해당 메서드를 호출했습니다. 즉, 부모 구성 요소가 업데이트 된 다음 두 번째 자식 구성 요소를 새 데이터로 업데이트 할 수 있습니다. 하지만 'this'를 바인딩하거나 화살표 기능을 사용해야합니다.

이것은 아이들이 특정 공유 서비스가 필요하지 않기 때문에 서로 연결되지 않는다는 이점이 있습니다.

이것이 최선의 방법이라고 확신하지 못합니다. 다른 사람들의 의견을 듣는 것이 흥미로울 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.