Javascript Angular 2 兄弟组件通信

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35884451/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 18:20:21  来源:igfitidea点击:

Angular 2 Sibling Component Communication

javascriptangulartypescript

提问by dennis.sheppard

I have a ListComponent. When an item is clicked in ListComponent, the details of that item should be shown in DetailComponent. Both are on the screen at the same time, so there's no routing involved.

我有一个列表组件。当在 ListComponent 中单击某个项目时,该项目的详细信息应显示在 DetailComponent 中。两者同时出现在屏幕上,因此不涉及路由。

How do I tell DetailComponent what item in ListComponent was clicked?

我如何告诉 DetailComponent ListComponent 中的哪个项目被点击?

I've considered emitting an event up to the parent (AppComponent), and have the parent set the selectedItem.id on DetailComponent with an @Input. Or I could use a shared service with observable subscriptions.

我已经考虑向父级 (AppComponent) 发出一个事件,并让父级在 DetailComponent 上使用 @Input 设置 selectedItem.id。或者我可以使用具有可观察订阅的共享服务。



EDIT:Setting the selected item via event + @Input doesn't trigger the DetailComponent, though, in case I were to need to execute additional code. So I'm not sure this is an acceptable solution.

编辑:通过 event + @Input 设置所选项目不会触发 DetailComponent,不过,以防我需要执行其他代码。所以我不确定这是一个可以接受的解决方案。



But both of these methods seem far more complex than the Angular 1 way of doing things which was either through $rootScope.$broadcast or $scope.$parent.$broadcast.

但是这两种方法似乎都比通过 $rootScope.$broadcast 或 $scope.$parent.$broadcast 的 Angular 1 做事方式复杂得多。

With everything in Angular 2 being a component, I'm surprised there's not more information out there about component communication.

由于 Angular 2 中的所有内容都是一个组件,我很惊讶没有更多关于组件通信的信息。

Is there another/more straightforward way to accomplish this?

是否有另一种/更直接的方法来实现这一目标?

回答by Alex J

Updated to rc.4:When trying to get data passed between sibling components in angular 2, The simplest way right now (angular.rc.4) is to take advantage of angular2's hierarchal dependency injection and create a shared service.

更新到 rc.4:当尝试在 angular 2 中的兄弟组件之间传递数据时,现在最简单的方法(angular.rc.4)是利用 angular2 的分层依赖注入并创建共享服务。

Here would be the service:

这将是服务:

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

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

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

Now, here would be the PARENT component

现在,这里将是 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{

} 

and its two children

和它的两个孩子

child 1

孩子 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;
    }
}

child 2 (It's sibling)

孩子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: Things to take note of when using this method.

现在:使用此方法时要注意的事项。

  1. Only include the service provider for the shared service in the PARENT component and NOT the children.
  2. You still have to include constructors and import the service in the children
  3. This answer was originally answered for an early angular 2 beta version. All that has changed though are the import statements, so that is all you need to update if you used the original version by chance.
  1. 仅在 PARENT 组件中包含共享服务的服务提供者,而不是子组件。
  2. 您仍然必须包含构造函数并在子项中导入服务
  3. 这个答案最初是为早期的 angular 2 beta 版本回答的。不过,所有已更改的是导入语句,因此,如果您偶然使用了原始版本,则只需更新这些语句。

回答by Dudi

In case of 2 different components (not nested components, parent\child\grandchild ) I suggest you this:

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

Source: Parent and children communicate via a service

来源:父母和孩子通过服务进行交流

回答by Caner

One way to do this is using a shared service.

一种方法是使用共享服务

However I find the following solution much simpler, it allows to share data between 2 siblings.(I tested this only on Angular 5)

但是我发现以下解决方案更简单,它允许在 2 个兄弟姐妹之间共享数据。(我仅在Angular 5上测试过)

In you parent component template:

在您的父组件模板中:

<!-- 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

app-sibling2.component.ts

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

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

回答by Jo?o Silva

There is a discussion about it here.

有一个关于它的讨论here。

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

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

Alex J's answer is good but it no longer works with current Angular 4 as of July, 2017.

Alex J 的回答很好,但截至 2017 年 7 月,它不再适用于当前的 Angular 4。

And this plunker link would demonstrate how to communicate between siblings using shared service and observable.

这个 plunker 链接将演示如何使用共享服务和 observable 在兄弟姐妹之间进行通信。

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

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

回答by Simon_Weaver

A directive can make sense in certain situations to 'connect' components. In fact the things being connected don't even need to be full components, and sometimes it's more lightweight and actually simpler if they aren't.

在某些情况下,指令可以有意义地“连接”组件。事实上,被连接的东西甚至不需要是完整的组件,有时它更轻巧,如果不是的话,实际上更简单。

For example I've got a Youtube Playercomponent (wrapping Youtube API) and I wanted some controller buttons for it. The only reason the buttons aren't part of my main component is that they're located elsewhere in the DOM.

例如,我有一个Youtube Player组件(包装 Youtube API),我想要一些控制器按钮。按钮不是我的主要组件的一部分的唯一原因是它们位于 DOM 中的其他地方。

In this case it's really just an 'extension' component that will only ever be of use with the 'parent' component. I say 'parent', but in the DOM it is a sibling - so call it what you will.

在这种情况下,它实际上只是一个“扩展”组件,只会与“父”组件一起使用。我说“父母”,但在 DOM 中它是一个兄弟姐妹 - 所以你可以随意称呼它。

Like I said it doesn't even need to be a full component, in my case it's just a <button>(but it could be a component).

就像我说的,它甚至不需要是一个完整的组件,在我的情况下它只是一个<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
   }
}

In the HTML for ProductPage.component, where youtube-playeris obviously my component that wraps the Youtube API.

在用于 的 HTML 中ProductPage.componentyoutube-player很明显我的组件在哪里包装了 Youtube API。

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

... lots more DOM ...

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

The directive hooks everything up for me, and I don't have to declare the (click) event in the HTML.

该指令为我连接了一切,我不必在 HTML 中声明 (click) 事件。

So the directive can nicely connect to the video player without having to involve ProductPageas a mediator.

因此,该指令可以很好地连接到视频播放器,而无需ProductPage作为中介参与。

This is the first time I've actually done this, so not yet sure how scalable it might be for much more complex situations. For this though I'm happy and it leaves my HTML simple and responsibilities of everything distinct.

这是我第一次真正做到这一点,所以还不确定它在更复杂的情况下的可扩展性如何。为此,虽然我很高兴,但它使我的 HTML 变得简单,并且让我对所有事情的责任都不同。

回答by Prashant M Bhavsar

Here is simple practical explanation:Simply explained here

这里是简单实用的解释:这里简单解释

In call.service.ts

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

Component from where you want to call observable to inform another component that button is clicked

您想要调用 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');
  }
}

Component where you want to perform action on button clicked on another component

您想要对单击另一个组件的按钮执行操作的组件

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

});

}

My understanding clear on components communication by reading this: http://musttoknow.com/angular-4-angular-5-communicate-two-components-using-observable-subject/

通过阅读以下内容,我对组件通信的理解很清楚:http: //musttoknow.com/angular-4-angular-5-communicate-two-components-using-observable-subject/

回答by Nick Greaves

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.

对于这个问题,共享服务是一个很好的解决方案。如果您也想存储一些活动信息,您可以将共享服务添加到您的主模块 (app.module) 提供程序列表中。

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

显然,列出组件共享的数据可以是任何东西。希望这可以帮助。

回答by Vereb

You need to set up the parent-child relationship between your components. The problem is that you might simply inject the child components in the constructor of the parent component and store it in a local variable. Instead, you should declare the child components in your parent component by using the @ViewChildproperty declarator. This is how your parent component should look like:

您需要设置组件之间的父子关系。问题是您可能只是在父组件的构造函数中注入子组件并将其存储在局部变量中。相反,您应该使用@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/api/core/index/ViewChild-var.html

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

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

Beware, the child component will not be available in the constructor of the parent component, just after the ngAfterViewInitlifecycle hook is called. To catch this hook simple implement the AfterViewInitinterface in you parent class the same way you would do with OnInit.

请注意,在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/

但是,如本博客说明中所述,还有其他属性声明符:http: //blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/

回答by ValRob

Behaviour subjects.I wrote a blogabout 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.

在这个例子中,我声明了一个类型为 number 的 noid 行为主体。它也是一个可观察的。如果“发生了什么事”,这将随着 new(){} 函数而改变。

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.

例如,我从 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) 
}

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:

从另一方面,我可以询问该 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']); 
              } 
          }) 
    }) 
}

回答by micronyks

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

我很惊讶没有更多关于组件通信的信息<=> 考虑 angualr2 的这个教程

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

对于兄弟组件通信,我建议使用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,[]);

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 angualr2for more information.

编辑:这是一个非常小的演示。您已经提到您已经尝试过使用sharedService. 因此,请考虑 angualr2 的本教程以获取更多信息。