Komunikasi Komponen Saudara Sudut 2

118

Saya memiliki ListComponent. Ketika sebuah item diklik di ListComponent, rincian item itu harus ditampilkan di DetailComponent. Keduanya ada di layar pada saat bersamaan, jadi tidak ada perutean yang terlibat.

Bagaimana cara memberi tahu DetailComponent item apa di ListComponent yang diklik?

Saya telah mempertimbangkan untuk memancarkan acara hingga induknya (AppComponent), dan meminta induk mengatur selectedItem.id di DetailComponent dengan @Input. Atau saya bisa menggunakan layanan bersama dengan langganan yang dapat diamati.


EDIT: Mengatur item yang dipilih melalui event + @Input tidak memicu DetailComponent, meskipun, jika saya perlu menjalankan kode tambahan. Jadi saya tidak yakin ini adalah solusi yang dapat diterima.


Tetapi kedua metode ini tampaknya jauh lebih kompleks daripada cara Angular 1 dalam melakukan sesuatu melalui $ rootScope. $ Broadcast atau $ scope. $ Parent. $ Broadcast.

Dengan semua yang ada di Angular 2 menjadi sebuah komponen, saya terkejut tidak ada lebih banyak informasi di luar sana tentang komunikasi komponen.

Apakah ada cara lain / lebih mudah untuk mencapai ini?

dennis.sheppard
sumber
Apakah Anda menemukan cara untuk berbagi data saudara? Saya membutuhkannya sebagai yang dapat diamati ..
Human Being

Jawaban:

65

Diperbarui ke rc.4: Ketika mencoba untuk mendapatkan data yang diteruskan antara komponen saudara di angular 2, Cara termudah saat ini (angular.rc.4) adalah dengan memanfaatkan injeksi ketergantungan hierarki angular2 dan membuat layanan bersama.

Inilah layanannya:

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

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

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

Sekarang, inilah komponen 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{

} 

dan kedua anaknya

anak 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;
    }
}

anak 2 (Itu saudara kandung)

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

SEKARANG: Hal-hal yang perlu diperhatikan saat menggunakan metode ini.

  1. Hanya sertakan penyedia layanan untuk layanan bersama di komponen PARENT dan BUKAN anak-anak.
  2. Anda masih harus menyertakan konstruktor dan mengimpor layanan pada anak-anak
  3. Jawaban ini awalnya dijawab untuk versi beta 2 sudut awal. Semua yang telah berubah adalah pernyataan impor, jadi hanya itu yang perlu Anda perbarui jika Anda menggunakan versi asli secara kebetulan.
Alex J.
sumber
2
Apakah ini masih berlaku untuk angular-rc1?
Sergio
4
Saya tidak percaya ini memberi tahu saudara kandungnya bahwa ada sesuatu yang telah diperbarui di layanan bersama. Jika child-component1 melakukan sesuatu yang perlu ditanggapi oleh child-component2, metode ini tidak akan menanganinya. Saya percaya sebaliknya adalah dengan yang dapat diamati?
dennis.sheppard
1
@Sufyan: Saya akan menebak bahwa menambahkan bidang penyedia ke anak-anak menyebabkan Angular membuat instance pribadi baru untuk masing-masing. Saat Anda tidak menambahkannya, mereka menggunakan instance "tunggal" induknya.
Ralph
1
Sepertinya ini tidak berfungsi lagi dengan pembaruan terkini
Sufyan Jabr
3
Ini sudah ketinggalan zaman. directivestidak lagi dideklarasikan di komponen.
Nate Gardner
26

Dalam kasus 2 komponen yang berbeda (bukan komponen bersarang, induk \ anak \ cucu) saya sarankan Anda ini:

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

}

AstronotKomponen:

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

Sumber: Orang tua dan anak berkomunikasi melalui suatu layanan

Dudi
sumber
2
Apakah Anda ingin menambahkan beberapa terminologi untuk jawaban ini. Saya percaya itu sejalan dengan RxJS, pola Observable, dll .. tidak sepenuhnya yakin, tetapi menambahkan deskripsi ke beberapa ini akan bermanfaat bagi orang-orang (seperti saya).
karns
13

Salah satu cara untuk melakukannya adalah dengan menggunakan layanan bersama .

Namun saya menemukan solusi berikut jauh lebih sederhana, ini memungkinkan untuk berbagi data antara 2 saudara kandung. (Saya menguji ini hanya di Angular 5 )

Di dalam template komponen induk Anda:

<!-- 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;
   ...
}
Caner
sumber
Bukankah ini bertentangan dengan gagasan kopling longgar dan dengan demikian komponen?
Robin
Ada yang tahu apakah ini cara yang bersih atau kotor? Tampaknya jauh lebih mudah untuk berbagi data ke satu arah, misalnya hanya dari sibiling1 ke sibiling2, tetapi tidak sebaliknya
Sarah
7

Sebuah arahan dapat masuk akal dalam situasi tertentu untuk 'menghubungkan' komponen. Faktanya hal-hal yang terhubung bahkan tidak perlu komponen lengkap, dan terkadang lebih ringan dan sebenarnya lebih sederhana jika tidak.

Misalnya saya punya Youtube Playerkomponen (membungkus Youtube API) dan saya ingin beberapa tombol pengontrol untuk itu. Satu-satunya alasan tombol bukan bagian dari komponen utama saya adalah karena tombol tersebut berada di tempat lain di DOM.

Dalam hal ini sebenarnya hanya komponen 'ekstensi' yang hanya akan digunakan dengan komponen 'induk'. Saya mengatakan 'orang tua', tetapi di DOM itu adalah saudara - jadi sebut saja sesuka Anda.

Seperti yang saya katakan itu bahkan tidak perlu menjadi komponen lengkap, dalam kasus saya itu hanya <button>(tapi bisa jadi komponen).

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

Dalam HTML untuk ProductPage.component, di mana youtube-playerjelas komponen saya yang membungkus API Youtube.

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

... lots more DOM ...

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

Direktif menghubungkan semuanya untuk saya, dan saya tidak perlu mendeklarasikan event (click) di HTML.

Jadi direktif dapat terhubung dengan baik ke pemutar video tanpa harus terlibat ProductPagesebagai mediator.

Ini adalah pertama kalinya saya benar-benar melakukan ini, jadi belum yakin seberapa skalabelnya untuk situasi yang jauh lebih kompleks. Untuk ini, saya senang dan meninggalkan HTML saya sederhana dan tanggung jawab atas segalanya berbeda.

Simon_Weaver
sumber
Salah satu konsep sudut yang paling penting untuk dipahami adalah bahwa komponen hanyalah arahan dengan templat. Setelah Anda benar-benar menghargai apa artinya ini maka arahan tidak terlalu menakutkan - dan Anda akan menyadari bahwa Anda dapat menerapkannya ke elemen apa pun untuk melampirkan perilakunya.
Simon_Weaver
Saya sudah mencoba ini tetapi saya mendapatkan kesalahan pengenal duplikat yang setara dengan player. Jika saya meninggalkan penyebutan pertama pemain saya mendapatkan rangeError. Saya bingung bagaimana ini seharusnya bekerja.
Katharine Osborne
@KatharineOsborne Sepertinya dalam kode saya yang sebenarnya saya gunakan _playeruntuk bidang pribadi yang mewakili pemain, jadi ya Anda akan mendapatkan kesalahan jika Anda menyalin ini dengan tepat. Akan diperbarui. Maaf!
Simon_Weaver
4

Berikut adalah penjelasan praktis sederhana: Dijelaskan secara sederhana di sini

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

Komponen dari mana Anda ingin memanggil observable untuk menginformasikan komponen lain bahwa tombol diklik

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

Komponen tempat Anda ingin melakukan tindakan pada tombol yang diklik pada komponen lain

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

});

}

Pemahaman saya jelas tentang komunikasi komponen dengan membaca ini: http://musttoknow.com/angular-4-angular-5-communicate-two-components-using-observable-subject/

Prashant M Bhavsar
sumber
Hei terima kasih banyak untuk solusi sederhana> saya mencobanya di stackblitz yang berfungsi dengan baik. Tetapi aplikasi saya memiliki rute yang dimuat lambat (telah menggunakan disediakan di: 'root') dan panggilan HTTP untuk mengatur dan mendapatkan. Bisakah Anda membantu saya dengan panggilan HTTP. mencoba banyak tetapi tidak berhasil:
Kshri
4

Layanan bersama adalah solusi yang baik untuk masalah ini. Jika Anda ingin menyimpan beberapa informasi aktivitas juga, Anda dapat menambahkan Layanan Bersama ke daftar penyedia modul utama (app.module) Anda.

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

Kemudian Anda bisa langsung menyediakannya ke komponen Anda,

constructor(private sharedService: SharedService)

Dengan Layanan Bersama, Anda dapat menggunakan fungsi atau membuat Subjek untuk memperbarui beberapa tempat sekaligus.

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

Dalam komponen daftar Anda, Anda dapat menerbitkan informasi item yang diklik,

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

lalu Anda dapat mengambil informasi ini di komponen detail Anda:

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

Jelasnya, data yang mencantumkan daftar komponen dapat berupa apa saja. Semoga ini membantu.

Nick Greaves
sumber
Ini adalah contoh paling gamblang (alias singkat) dari konsep layanan bersama ini, dan harus benar-benar di-upvoted untuk meningkatkan visibilitasnya, karena tidak ada jawaban yang diterima.
iGanja
3

Anda perlu menyiapkan hubungan induk-anak antara komponen Anda. Masalahnya adalah Anda mungkin hanya memasukkan komponen anak dalam konstruktor komponen induk dan menyimpannya dalam variabel lokal. Sebagai gantinya, Anda harus mendeklarasikan komponen anak dalam komponen induk Anda dengan menggunakan @ViewChilddeklarator properti. Seperti inilah tampilan komponen induk Anda:

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

Hati-hati, komponen anak tidak akan tersedia dalam konstruktor komponen induk, tepat setelah ngAfterViewInithook siklus hidup dipanggil. Untuk menangkap pengait ini, implementasikan AfterViewInitantarmuka di kelas induk Anda dengan cara yang sama seperti yang Anda lakukan OnInit.

Tapi, ada deklarator properti lain seperti yang dijelaskan dalam catatan blog ini: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/

Vereb
sumber
2

Subjek perilaku. Saya menulis blog tentang itu.

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

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

Dalam contoh ini saya mendeklarasikan subjek perilaku noid tipe nomor. Juga dapat diamati. Dan jika "sesuatu terjadi" ini akan berubah dengan fungsi baru () {}.

Jadi, dalam komponen sibling, satu akan memanggil fungsi, untuk melakukan perubahan, dan yang lainnya akan terpengaruh oleh perubahan itu, atau sebaliknya.

Misalnya, saya mendapatkan id dari URL dan memperbarui noid dari subjek perilaku.

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

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

Dan dari sisi lain, saya dapat menanyakan apakah ID itu "apa pun yang saya inginkan" dan membuat pilihan setelah itu, dalam kasus saya jika saya ingin menghapus tugas, dan tugas itu adalah url saat ini, ia harus mengarahkan saya ke rumah:

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

Ini sebenarnya bukan yang Anda inginkan tetapi yang pasti akan membantu Anda

Saya terkejut tidak ada lebih banyak informasi di luar sana tentang komunikasi komponen <=> pertimbangkan tutorial ini oleh angualr2

Untuk komunikasi komponen saudara, saya sarankan untuk pergi bersama sharedService. Ada juga opsi lain yang tersedia.

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

Silakan merujuk ke tautan di atas untuk kode lebih lanjut.

Sunting: Ini adalah demo yang sangat kecil. Anda sudah menyebutkan bahwa Anda sudah mencoba dengan sharedService. Jadi mohon pertimbangkan tutorial ini oleh angualr2 untuk informasi lebih lanjut.

mikronik
sumber
0

Saya telah menurunkan metode penyetel dari induk ke salah satu anaknya melalui pengikatan, memanggil metode itu dengan data dari komponen anak, yang berarti bahwa komponen induk diperbarui dan kemudian dapat memperbarui komponen anak keduanya dengan data baru. Itu memang membutuhkan pengikatan 'ini' atau menggunakan fungsi panah.

Ini memiliki keuntungan bahwa anak-anak tidak begitu terikat satu sama lain karena mereka tidak membutuhkan layanan bersama yang spesifik.

Saya tidak sepenuhnya yakin bahwa ini adalah praktik terbaik, akan menarik untuk mendengar pandangan orang lain tentang ini.

Thingamajig
sumber