Giao tiếp thành phần Angular 2 Sibling


118

Tôi có một ListComponent. Khi một mục được nhấp trong ListComponent, các chi tiết của mục đó sẽ được hiển thị trong DetailComponent. Cả hai đều xuất hiện trên màn hình cùng một lúc, vì vậy không có liên quan đến định tuyến.

Làm cách nào để cho DetailComponent biết mục nào trong ListComponent đã được nhấp vào?

Tôi đã xem xét việc phát ra một sự kiện lên đến cha mẹ (AppComponent) và yêu cầu cha mẹ đặt lựa chọnItem.id trên DetailComponent với một @Input. Hoặc tôi có thể sử dụng một dịch vụ được chia sẻ với các đăng ký có thể quan sát được.


CHỈNH SỬA: Tuy nhiên, việc đặt mục đã chọn thông qua sự kiện + @Input không kích hoạt DetailComponent, trong trường hợp tôi cần thực thi mã bổ sung. Vì vậy, tôi không chắc đây là một giải pháp chấp nhận được.


Nhưng cả hai phương pháp này dường như phức tạp hơn nhiều so với cách thực hiện của Angular 1 thông qua $ rootScope. $ Broadcast hoặc $ scope. $ Parent. $ Broadcast.

Với mọi thứ trong Angular 2 đều là một thành phần, tôi ngạc nhiên là không có thêm thông tin nào về giao tiếp thành phần.

Có cách nào khác / đơn giản hơn để thực hiện điều này không?


Bạn đã tìm thấy bất kỳ cách nào để chia sẻ dữ liệu anh chị em? Tôi cần nó như có thể quan sát được ..
Con người

Câu trả lời:


65

Đã cập nhật lên rc.4: Khi cố gắng truyền dữ liệu giữa các thành phần anh chị em trong angle 2, Cách đơn giản nhất ngay bây giờ (angle.rc.4) là tận dụng khả năng tiêm phụ thuộc phân cấp của angle2 và tạo một dịch vụ chia sẻ.

Đây sẽ là dịch vụ:

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

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

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

Bây giờ, đây sẽ là thành phần PHỤ HUYNH

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{

} 

và hai đứa con của nó

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

con 2 (Đó là anh chị em ruột)

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

NGAY BÂY GIỜ: Những điều cần lưu ý khi sử dụng phương pháp này.

  1. Chỉ bao gồm nhà cung cấp dịch vụ cho dịch vụ dùng chung trong thành phần PHỤ HUYNH chứ KHÔNG phải trẻ em.
  2. Bạn vẫn phải bao gồm các hàm tạo và nhập dịch vụ trong phần con
  3. Câu trả lời này ban đầu được trả lời cho phiên bản beta 2 góc đầu tiên. Tất cả những gì đã thay đổi mặc dù là các câu lệnh nhập, vì vậy đó là tất cả những gì bạn cần cập nhật nếu tình cờ sử dụng phiên bản gốc.

2
Điều này có còn hợp lệ cho angle-rc1 không?
Sergio

4
Tuy nhiên, tôi không tin rằng điều này lại thông báo cho anh chị em rằng có điều gì đó đã được cập nhật trong dịch vụ chia sẻ. Nếu child-component1 thực hiện điều gì đó mà child-component2 cần phản hồi, phương thức này không xử lý điều đó. Tôi tin rằng cách xung quanh đó là với các vật thể quan sát?
dennis.sheppard

1
@Sufyan: Tôi đoán rằng việc thêm các trường cung cấp cho các trường con sẽ khiến Angular tạo các trường hợp riêng tư mới cho mỗi trường. Khi bạn không thêm chúng, chúng sẽ sử dụng thể hiện "singleton" của cha mẹ.
Ralph

1
Ngoại hình như thế này không được làm việc nữa với các bản cập nhật mới nhất
Sufyan Jabr

3
Điều này đã lỗi thời. directiveskhông còn được khai báo trong thành phần.
Nate Gardner

26

Trong trường hợp có 2 thành phần khác nhau (không phải là các thành phần lồng nhau, parent \ child \ cháu), tôi đề nghị bạn điều này:

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

}

Phi hành gia

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

Nguồn: Cha mẹ và con cái giao tiếp qua dịch vụ


2
Có muốn bạn thêm một số thuật ngữ cho câu trả lời này. Tôi tin rằng nó phù hợp với RxJS, mô hình có thể quan sát, v.v. không hoàn toàn chắc chắn, nhưng việc thêm mô tả vào một số điều này sẽ có lợi cho mọi người (như bản thân tôi).
karns

13

Một cách để làm điều này là sử dụng dịch vụ chia sẻ .

Tuy nhiên, tôi thấy giải pháp sau đây đơn giản hơn nhiều, nó cho phép chia sẻ dữ liệu giữa 2 anh chị em. (Tôi chỉ thử nghiệm điều này trên Angular 5 )

Trong mẫu thành phần mẹ của bạn:

<!-- 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-Anh / chị / em ruột2.component.ts

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

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

Điều này không phản đối ý tưởng về khớp nối lỏng lẻo và do đó của các thành phần?
Robin

Bất cứ ai có bất kỳ ý tưởng cho dù đây là một cách sạch sẽ hay bẩn thỉu với nó? Có vẻ như đơn giản hơn rất nhiều để chia sẻ dữ liệu theo một hướng, ví dụ: chỉ từ sibiling1 đến sibiling2, nhưng không phải ngược lại
Sarah


7

Một chỉ thị có thể có ý nghĩa trong một số tình huống nhất định để 'kết nối' các thành phần. Trên thực tế, những thứ được kết nối thậm chí không cần phải có đầy đủ các thành phần, và đôi khi nó nhẹ hơn và thực sự đơn giản hơn nếu không.

Ví dụ: tôi có một Youtube Playerthành phần (gói API Youtube) và tôi muốn có một số nút điều khiển cho nó. Lý do duy nhất mà các nút không phải là một phần của thành phần chính của tôi là chúng nằm ở nơi khác trong DOM.

Trong trường hợp này, nó thực sự chỉ là một thành phần 'tiện ích mở rộng' sẽ chỉ được sử dụng với thành phần 'cha mẹ'. Tôi nói là 'cha mẹ', nhưng trong DOM, nó là anh chị em ruột - vì vậy hãy gọi nó như thế nào bạn muốn.

Giống như tôi đã nói, nó thậm chí không cần phải là một thành phần đầy đủ, trong trường hợp của tôi, nó chỉ là một <button>(nhưng nó có thể là một thành phần).

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

Trong HTML ProductPage.component, youtube-playerrõ ràng là thành phần của tôi bao bọc API Youtube.

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

... lots more DOM ...

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

Chỉ thị kết nối mọi thứ cho tôi và tôi không phải khai báo sự kiện (nhấp chuột) trong HTML.

Vì vậy, chỉ thị có thể kết nối độc đáo với trình phát video mà không cần phải tham gia ProductPagenhư một người hòa giải.

Đây là lần đầu tiên tôi thực sự làm điều này, vì vậy vẫn chưa chắc chắn về khả năng mở rộng của nó đối với các tình huống phức tạp hơn nhiều. Đối với điều này, mặc dù tôi rất vui và nó khiến HTML của tôi đơn giản và trách nhiệm của mọi thứ khác nhau.


Một trong những khái niệm góc cạnh quan trọng nhất cần hiểu là một thành phần chỉ là một chỉ thị với một khuôn mẫu. Một khi bạn thực sự đánh giá cao ý nghĩa của điều này thì các chỉ thị không quá đáng sợ - và bạn sẽ nhận ra rằng bạn có thể áp dụng chúng cho bất kỳ phần tử nào để gắn hành vi với nó.
Simon_Weaver

Tôi đã thử điều này nhưng gặp lỗi số nhận dạng trùng lặp tương đương với player. Nếu tôi bỏ qua đề cập đầu tiên về người chơi, tôi sẽ nhận được lỗi rangeError. Tôi bối rối không biết điều này sẽ hoạt động như thế nào.
Katharine Osborne

@KatharineOsborne Có vẻ như trong mã thực tế của tôi, tôi sử dụng _playercho trường riêng đại diện cho trình phát, vì vậy có, bạn sẽ gặp lỗi nếu bạn sao chép chính xác mã này. Sẽ nâng cấp. Lấy làm tiếc!
Simon_Weaver

4

Đây là giải thích thực tế đơn giản: Giải thích đơn giản ở đây

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

Thành phần từ nơi bạn muốn gọi là có thể quan sát để thông báo cho thành phần khác rằng nút được nhấp

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

Thành phần nơi bạn muốn thực hiện hành động trên nút được nhấp vào thành phần khác

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

});

}

Sự hiểu biết của tôi rõ ràng về giao tiếp các thành phần bằng cách đọc phần này: http://musttoknow.com/angular-4-angular-5-communicate-two-components-using-observable-subject/


Này, cảm ơn rất nhiều vì giải pháp đơn giản> tôi đã thử nó trong stackblitz và hoạt động tốt. Nhưng ứng dụng của tôi có các tuyến đường tải chậm (đã sử dụng cung cấp trong: 'root') và các cuộc gọi HTTP để đặt và nhận. Bạn có thể giúp tôi với các cuộc gọi HTTP. đã cố gắng rất nhiều nhưng không hiệu quả:
Kshri

4

Dịch vụ chia sẻ là một giải pháp tốt cho vấn đề này. Nếu bạn cũng muốn lưu trữ một số thông tin hoạt động, bạn có thể thêm Dịch vụ được chia sẻ vào danh sách nhà cung cấp mô-đun chính (app.module) của mình.

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

Sau đó, bạn có thể trực tiếp cung cấp nó cho các thành phần của mình,

constructor(private sharedService: SharedService)

Với Dịch vụ chia sẻ, bạn có thể sử dụng các chức năng hoặc có thể tạo Chủ đề để cập nhật nhiều nơi cùng một lúc.

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

Trong thành phần danh sách của bạn, bạn có thể xuất bản thông tin mục đã nhấp,

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

và sau đó bạn có thể tìm nạp thông tin này tại thành phần chi tiết của mình:

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

Rõ ràng, dữ liệu liệt kê các chia sẻ thành phần có thể là bất kỳ thứ gì. Hi vọng điêu nay co ich.


Đây là ví dụ rõ ràng nhất (hay còn gọi là ngắn gọn) về khái niệm dịch vụ chia sẻ này và thực sự nên được ủng hộ để tăng khả năng hiển thị của nó, vì không có câu trả lời được chấp nhận.
iGanja

3

Bạn cần thiết lập mối quan hệ cha-con giữa các thành phần của bạn. Vấn đề là bạn có thể chỉ cần đưa các thành phần con vào hàm tạo của thành phần mẹ và lưu trữ nó trong một biến cục bộ. Thay vào đó, bạn nên khai báo các thành phần con trong thành phần mẹ của mình bằng cách sử dụng bộ @ViewChildkhai báo thuộc tính. Đây là cách thành phần mẹ của bạn sẽ trông như thế nào:

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

Hãy lưu ý, thành phần con sẽ không có sẵn trong phương thức khởi tạo của thành phần mẹ, ngay sau khi ngAfterViewInithook vòng đời được gọi. Để bắt được hook này, hãy triển khai AfterViewInitgiao diện trong lớp cha giống như cách bạn làm với giao diện này OnInit.

Tuy nhiên, có những trình khai báo tài sản khác như được giải thích trong ghi chú blog này: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/


2

Chủ thể hành vi. Tôi đã viết một blog về điều đó.

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

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

Trong ví dụ này, tôi đang khai báo một chủ thể hành vi noid của kiểu số. Ngoài ra nó là một quan sát được. Và nếu "điều gì đó xảy ra", điều này sẽ thay đổi với hàm mới () {}.

Vì vậy, trong các thành phần của anh chị em, một người sẽ gọi hàm, để thực hiện thay đổi, và thành phần còn lại sẽ bị ảnh hưởng bởi sự thay đổi đó hoặc ngược lại.

Ví dụ: tôi lấy id từ URL và cập nhật noid từ chủ thể hành vi.

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

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

Và từ phía bên kia, tôi có thể hỏi xem ID đó có phải là "những gì tôi muốn" không và đưa ra lựa chọn sau đó, trong trường hợp của tôi, nếu tôi muốn xóa một nhiệm vụ và nhiệm vụ đó là url hiện tại, nó phải chuyển hướng tôi. Đến nhà:

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

Đây không phải là điều bạn muốn nhưng chắc chắn sẽ giúp bạn

Tôi ngạc nhiên là không có thêm thông tin về giao tiếp thành phần <=> hãy xem hướng dẫn này của angualr2

Đối với giao tiếp các thành phần anh chị em, tôi khuyên bạn nên đi cùng sharedService. Ngoài ra còn có các tùy chọn khác có sẵn.

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

Vui lòng tham khảo liên kết ở đầu để biết thêm mã.

Chỉnh sửa: Đây là một bản demo rất nhỏ. Bạn đã đề cập rằng bạn đã thử với sharedService. Vì vậy, vui lòng xem xét hướng dẫn này của angualr2 để biết thêm thông tin.


0

Tôi đã truyền các phương thức setter từ cha mẹ đến một trong các phương thức con của nó thông qua một ràng buộc, gọi phương thức đó với dữ liệu từ thành phần con, nghĩa là thành phần cha được cập nhật và sau đó có thể cập nhật thành phần con thứ hai của nó với dữ liệu mới. Tuy nhiên, nó yêu cầu ràng buộc 'this' hoặc sử dụng hàm mũi tên.

Điều này có lợi ích là những đứa trẻ không được kết hợp với nhau vì chúng không cần một dịch vụ dùng chung cụ thể.

Tôi không hoàn toàn chắc chắn rằng đây là phương pháp hay nhất, sẽ rất thú vị khi nghe những người khác quan điểm về điều này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.