Angular 2 Kardeş Bileşen İletişimi


118

ListComponent'ım var. ListComponent'te bir öğeye tıklandığında, bu öğenin ayrıntıları DetailComponent'te gösterilmelidir. Her ikisi de ekranda aynı anda olduğundan, yönlendirme yoktur.

DetailComponent'e ListComponent'daki hangi öğeye tıklandığını nasıl söylerim?

Ebeveyne (AppComponent) kadar bir olay yaymayı ve ebeveynin DetailComponent üzerinde bir @Input ile selectedItem.id ayarlamasını sağladım. Ya da gözlemlenebilir aboneliklere sahip paylaşılan bir hizmet kullanabilirim.


DÜZENLEME: Seçili öğeyi event + @Input ile ayarlamak, ek kod çalıştırmam gerekmesi durumunda DetailComponent'i tetiklemez. Bu yüzden bunun kabul edilebilir bir çözüm olduğundan emin değilim.


Ancak bu yöntemlerin her ikisi de, Angular 1'in $ rootScope. $ Broadcast veya $cope. $ Parent. $ Broadcast yoluyla yapılmasından çok daha karmaşık görünmektedir.

Angular 2'deki her şey bir bileşen olduğundan, bileşen iletişimi hakkında daha fazla bilgi olmamasına şaşırdım.

Bunu başarmanın başka / daha basit bir yolu var mı?


Kardeş veri paylaşımı için herhangi bir yol buldunuz mu? Ona gözlemlenebilir olarak ihtiyacım var ..
Human Being

Yanıtlar:


65

Rc.4'e güncellendi: Açısal 2'de kardeş bileşenler arasında veri aktarımı yapmaya çalışırken, şu anda en basit yol (angular.rc.4), angular2'nin hiyerarşik bağımlılık enjeksiyonundan yararlanmak ve paylaşılan bir hizmet oluşturmaktır.

İşte hizmet olacaktır:

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

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

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

Şimdi, burada EBEVEYN bileşeni olacaktır

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{

} 

ve iki çocuğu

çocuk 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;
    }
}

çocuk 2 (kardeş)

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

ŞİMDİ: Bu yöntemi kullanırken dikkat edilmesi gerekenler.

  1. Yalnızca ebeveyn bileşenine paylaşılan hizmet için servis sağlayıcısını dahil edin, çocukları DEĞİL.
  2. Yine de kurucuları dahil etmeniz ve hizmeti çocuklara aktarmanız gerekir
  3. Bu cevap başlangıçta erken bir açısal 2 beta versiyonu için cevaplandı. Yine de değişen tek şey içe aktarma ifadeleridir, bu nedenle orijinal sürümü şans eseri kullandıysanız güncellemeniz gereken tek şey budur.

2
Bu hala angular-rc1 için geçerli mi?
Sergio

4
Bunun kardeşe paylaşılan hizmette bir şeylerin güncellendiğini bildirdiğine inanmıyorum. Child-component1, child-component2'nin yanıt vermesi gereken bir şeyi yaparsa, bu yöntem bunu işlemez. Bunun gözlemlenebilirler olduğuna inanıyorum.
dennis.sheppard

1
@Sufyan: Sağlayıcı alanlarının çocuklara eklenmesinin Angular'ın her biri için yeni özel durumlar oluşturmasına neden olduğunu tahmin edeceğim. Bunları eklemediğinizde, ebeveynin "tekli" örneğini kullanırlar.
Ralph

1
Görünüşe göre bu en son güncellemelerle artık çalışmıyor
Sufyan Jabr

3
Bu modası geçmiş. directivesartık bileşende belirtilmez.
Nate Gardner

26

2 farklı bileşen olması durumunda (iç içe bileşenler değil, üst \ alt \ torun) size şunu öneririm:

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

Kaynak: Ebeveyn ve çocuklar bir hizmet aracılığıyla iletişim kurar


2
Bu yanıta biraz terminoloji eklemenizi isterim. Bunun RxJS, Gözlemlenebilir model vb. İle uyumlu olduğuna inanıyorum. Tam olarak emin değilim, ancak bazılarına açıklamalar eklemek (benim gibi) insanlar için faydalı olacaktır.
karns

13

Bunu yapmanın bir yolu, paylaşılan bir hizmet kullanmaktır .

Ancak aşağıdaki çözümü çok daha basit buluyorum, 2 kardeş arasında veri paylaşımına izin veriyor. (Bunu sadece Angular 5 üzerinde test ettim )

Üst bileşen şablonunuzda:

<!-- 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;
   ...
}

Bu, gevşek bağlantı fikrine ve dolayısıyla bileşenlere karşı gelmiyor mu?
Robin

Bunun temiz mi yoksa kirli bir yol mu olduğu hakkında bir fikri olan var mı? Verileri tek bir yönde paylaşmak çok daha basit görünüyor, örneğin yalnızca sibiling1'den sibiling2'ye kadar, ancak tam tersi değil
Sarah


7

Bir yönerge, belirli durumlarda bileşenleri 'bağlamak' mantıklı olabilir. Aslında bağlanan şeylerin tam bileşen olması gerekmez ve bazen daha hafiftir ve değilse daha basittir.

Örneğin, bir Youtube Playerbileşenim var (Youtube API'sini sarmak) ve bunun için bazı kontrol düğmeleri istedim. Düğmelerin ana bileşenimin bir parçası olmamasının tek nedeni, DOM'da başka bir yerde bulunmalarıdır.

Bu durumda, yalnızca "ana" bileşenle kullanılabilecek bir "uzantı" bileşenidir. Ben 'ebeveyn' diyorum, ancak DOM'da bir kardeştir - bu yüzden ne isterseniz onu adlandırın.

Dediğim gibi, tam bir bileşen olması gerekmiyor, benim durumumda bu sadece bir <button>(ama bir bileşen olabilir).

@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'de , Youtube API'sini saran bileşenimin açıkça ProductPage.componentbulunduğu youtube-playeryer.

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

... lots more DOM ...

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

Yönerge her şeyi benim için bağlar ve HTML'de (tıklama) olayını bildirmek zorunda değilim.

Böylece yönerge, ProductPagearacı olarak dahil olmak zorunda kalmadan video oynatıcıya güzel bir şekilde bağlanabilir .

Bunu aslında ilk kez yapıyorum, bu yüzden çok daha karmaşık durumlarda ne kadar ölçeklenebilir olabileceğinden henüz emin değilim. Bunun için yine de mutluyum ve HTML'mi basit ve her şeyin sorumluluklarını farklı bırakıyor.


Anlaşılması gereken en önemli açısal kavramlardan biri, bir bileşenin sadece şablonlu bir yönerge olduğudur. Bunun ne anlama geldiğini gerçekten anladığınızda, direktifler o kadar korkutucu değildir - ve davranış eklemek için bunları herhangi bir öğeye uygulayabileceğinizi fark edeceksiniz.
Simon_Weaver

Bunu denedim ama eşdeğeri için yinelenen bir tanımlayıcı hatası alıyorum player. Oyuncunun ilk sözünü bir kenara bırakırsam, bir rangeError hatası alırım. Bunun nasıl çalışacağı konusunda şaşkınım.
Katharine Osborne

@KatharineOsborne Gerçek kodumda _player, oyuncuyu temsil eden özel alan için kullandığım gibi görünüyor , bu yüzden evet, bunu tam olarak kopyalarsanız bir hata alırsınız. Güncellenecek. Afedersiniz!
Simon_Weaver

4

İşte basit pratik açıklama: Burada basitçe açıklanmıştır

Call.service.ts içinde

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

Başka bir bileşene düğmenin tıklandığını bildirmek için gözlemlenebilir olarak çağırmak istediğiniz bileşen

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

Başka bir bileşene tıklanan düğmeyle eylem gerçekleştirmek istediğiniz bileşen

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

});

}

Bunu okuyarak bileşenlerin iletişimini anladığım kadar net: http://musttoknow.com/angular-4-angular-5-communicate-two-components-using-observable-subject/


Hey, basit çözüm için çok teşekkürler> İyi çalışan stackblitz'de denedim. Ancak uygulamamda tembel yüklenen yollar (sağlanan: 'root' kullanılıyor) ve ayarlamak ve almak için HTTP çağrıları var. HTTP aramalarında bana yardım eder misin? çok denedi ama işe yaramıyor:
Kshri

4

Paylaşılan hizmet, bu sorun için iyi bir çözümdür. Bazı etkinlik bilgilerini de saklamak istiyorsanız, Paylaşılan Hizmeti ana modüller (uygulama modülü) sağlayıcı listenize ekleyebilirsiniz.

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

Daha sonra doğrudan bileşenlerinize sağlayabilirsiniz,

constructor(private sharedService: SharedService)

Paylaşılan Hizmet ile işlevleri kullanabilir veya birden çok yeri aynı anda güncellemek için bir Konu oluşturabilirsiniz.

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

Liste bileşeninizde tıklanan öğe bilgilerini yayınlayabilirsiniz,

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

ve daha sonra bu bilgileri ayrıntı bileşeninizden alabilirsiniz:

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

Açıkçası, bileşen paylaşımlarını listeleyen veriler herhangi bir şey olabilir. Bu yardımcı olur umarım.


Bu, paylaşılan bir hizmet kavramının en basit (kısaca) örneğidir ve kabul edilen bir cevap olmadığından, görünürlüğünü artırmak için gerçekten yükseltilmelidir.
iGanja

3

Bileşenleriniz arasında ebeveyn-çocuk ilişkisini kurmanız gerekir. Sorun şu ki, alt bileşenleri ana bileşenin yapıcısına enjekte edip yerel bir değişkende depolayabilirsiniz. Bunun yerine, @ViewChildözellik tanımlayıcısını kullanarak üst bileşeninizdeki alt bileşenleri bildirmelisiniz . Ana bileşeninizin şöyle görünmesi gerekir:

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

Dikkat edin, alt bileşen, ngAfterViewInityaşam döngüsü kancası çağrıldıktan hemen sonra üst bileşenin yapıcısında kullanılamayacaktır . Bu kancayı yakalamak için, AfterViewInitana sınıfınızdaki arayüzü, yaptığınız gibi uygulayın OnInit.

Ancak, bu blog notunda açıklandığı gibi başka mülk beyan edicileri de vardır: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/


2

Davranış konuları. Bununla ilgili bir blog yazdım .

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

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

Bu örnekte, tip numarası olan bir noid davranış konusu bildiriyorum. Ayrıca gözlemlenebilir. Ve eğer "bir şey olursa" bu, new () {} işleviyle değişecektir.

Yani, kardeşin bileşenlerinde, biri değişikliği yapmak için işlevi çağıracak ve diğeri bu değişiklikten etkilenecek veya tam tersi.

Örneğin, kimliği URL'den alıyorum ve davranış konusundan noid'i güncelliyorum.

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

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

Ve diğer taraftan, bu kimliğin "ne istersem" olup olmadığını sorabilirim ve bundan sonra bir seçim yapabilirim, benim durumumda bir görevi silmek istiyorsam ve bu görev mevcut url ise, beni yeniden yönlendirmesi gerekir eve:

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

Bu tam olarak istediğin şey değil ama kesinlikle sana yardımcı olacak

Bileşen iletişimi hakkında daha fazla bilgi olmamasına şaşırdım <=> angualr2 tarafından yazılan bu eğiticiyi inceleyin

Kardeş bileşen iletişimi için, birlikte gitmeyi öneririm sharedService. Yine de başka seçenekler de var.

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

Daha fazla kod için lütfen üstteki bağlantıya bakın.

Düzenleme: Bu çok küçük bir demodur. Daha önce denediğinizi zaten belirtmiştiniz sharedService. Bu nedenle , daha fazla bilgi için lütfen angualr2 tarafından hazırlanan bu öğreticiyi inceleyin .


0

Ayarlayıcı yöntemlerini üst öğeden alt bileşenlerinden birine bir bağlama yoluyla aktarıyorum, bu yöntemi alt bileşenden gelen verilerle çağırıyorum, yani ana bileşenin güncellendiği ve ardından ikinci alt bileşenini yeni verilerle güncelleyebildiği anlamına geliyor. Yine de 'bunu' bağlamayı veya bir ok işlevini kullanmayı gerektirir.

Bu, çocukların belirli bir ortak hizmete ihtiyaç duymadıkları için birbirleriyle çok fazla bağlantılı olmaması avantajına sahiptir.

Bunun en iyi uygulama olduğundan tam olarak emin değilim, başkalarının bu konudaki görüşlerini duymak ilginç olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.