program tip

Angular 2 형제 구성 요소 통신

radiobox 2020. 8. 10. 07:52
반응형

Angular 2 형제 구성 요소 통신


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

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

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


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


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

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

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


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

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


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

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

여기에 그것에 대한 논의가 있습니다.

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

Alex J의 대답은 좋지만 2017 년 7 월 현재 현재 Angular 4에서는 더 이상 작동하지 않습니다.

그리고이 플 런커 링크는 공유 서비스와 관찰 가능을 사용하여 형제들간에 통신하는 방법을 보여줍니다.

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


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

예를 들어 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은 단순하고 모든 것에 대한 책임은 구별됩니다.


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

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


구성 요소 간의 상위-하위 관계를 설정해야합니다. 문제는 단순히 부모 구성 요소의 생성자에 자식 구성 요소를 삽입하고 로컬 변수에 저장할 수 있다는 것입니다. 대신 @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.

But, there are other property declarators as explained in this blog note: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/


Behaviour subjects. I wrote a blog about that.

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

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

In this example i am declaring a noid behavior subject of type number. Also it is an observable. And if "something happend" this will change with the new(){} function.

So, in the sibling's components, one will call the function, to make the change, and the other one will be affected by that change, or vice-versa.

For example, I get the id from the URL and update the noid from the behavior subject.

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

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

And from the other side, I can ask if that ID is "what ever i want" and make a choice after that, in my case if i want to delte a task, and that task is the current url, it have to redirect me to the home:

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

This is not what you exactly want but for sure will help you out

I'm surprised there's not more information out there about component communication <=> consider this tutorial by angualr2

For sibling components communication, I'd suggest to go with sharedService. There are also other options available though.

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,[]);

Please refer to link at top for more code.

Edit: This is a very small demo. You have already mention that you have already tried with sharedService. So please consider this tutorial by angualr2 for more information.


Shared service is a good solution for this issue. If you want to store some activity information too, you can add Shared Service to your main modules (app.module) provider list.

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

Then you can directly provide it to your components,

constructor(private sharedService: SharedService)

With Shared Service you can either use functions or you can create a Subject to update multiple places at once.

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

In your list component you can publish clicked item information,

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

and then you can fetch this information at your detail component:

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

Obviously, the data that list component shares can be anything. Hope this helps.


I have been passing down setter methods from the parent to one of its children through a binding, calling that method with the data from the child component, meaning that the parent component is updated and can then update its second child component with the new data. It does require binding 'this' or using an arrow function though.

This has the benefit that the children aren't so coupled to each other as they don't need a specific shared service.

I am not entirely sure that this is best practice, would be interesting to hear others views on this.

참고URL : https://stackoverflow.com/questions/35884451/angular-2-sibling-component-communication

반응형