Кутове з'єднання компонентів 2


118

У мене є ListComponent. При натисканні елемента в ListComponent, його деталі повинні відображатися в DetailComponent. Обидва знаходяться на екрані одночасно, тому маршрутизація не бере участь.

Як мені повідомити DetailComponent, на який елемент в ListComponent було натиснуто?

Я розглядав можливість передачі події до батьківського (AppComponent), і батько встановив обранийItem.id на DetailComponent за допомогою @Input. Або я міг би використовувати спільний сервіс із спостережуваними підписками.


РЕДАКТУВАННЯ: Встановлення вибраного елемента за допомогою події + @ Введення не запускає DetailComponent, на випадок, якщо мені потрібно буде виконати додатковий код. Тож я не впевнений, що це прийнятне рішення.


Але обидва ці способи здаються набагато складнішими, ніж спосіб кутового 1, який було здійснено через $ rootScope. $ Broadcast або $ range. $ Parent. $ Broadcast.

Оскільки все в Angular 2 є компонентом, я здивований, що там немає більше інформації про компоненти зв'язку.

Чи існує інший / більш простий спосіб досягти цього?


Ви знайшли який-небудь спосіб для спільного використання даних? Мені це потрібно як спостережливе ..
Людина Буття

Відповіді:


65

Оновлено до rc.4: При спробі отримати дані, передані між компонентами братів у куті 2, найпростіший спосіб зараз (angular.rc.4) - скористатися перевагою введення ієрархальної залежності ієрархії angular2 та створити спільну послугу.

Ось послуга:

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

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

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

Тепер тут буде компонент РОДИНИ

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

ЗАРАЗ: Речі, які слід враховувати при використанні цього методу.

  1. Включіть лише постачальника послуг для спільної послуги в компонент PARENT, а НЕ дітей.
  2. Вам ще потрібно включити конструктори та імпортувати послугу для дітей
  3. На цю відповідь спочатку відповіли для ранньої кутової бета-версії. Все, що змінилося, хоч і є імпортними заявами, так що це все, що вам потрібно оновити, якщо ви випадково використовували оригінальну версію.

2
Це все ще справедливо для angular-rc1?
Серхіо

4
Я не вірю, що це сповіщає побратимів, що щось було оновлено в спільній службі. Якщо дочірній компонент1 робить щось, на що повинен відповідати дочірній компонент2, цей метод не справляється з цим. Я вважаю, що навколо цього спостерігається спостереження?
dennis.sheppard

1
@Sufyan: Я буду здогадуватися, що додавання полів провайдера до дітей спричиняє Angular створювати нові приватні екземпляри для кожного. Якщо ви не додаєте їх, вони використовують батьківський екземпляр "singleton".
Ральф

1
Схоже, це більше не працює з останніми оновленнями
Sufyan Jabr

3
Це застаріло. directivesбільше не оголошуються в компоненті.
Нейт Гарднер

26

Якщо є два різних компоненти (не вкладені компоненти, батько \ дитина \ онук), пропоную вам це:

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

Джерело: Батько та діти спілкуються через службу


2
Хочете, щоб ви додали трохи термінології до цієї відповіді. Я вважаю, що це відповідатиме RxJS, спостережуваному шаблону тощо. Не зовсім впевнений, але додавання описів до чогось із цього буде корисним для людей (як я).
karns

13

Один із способів зробити це - використання спільної послуги .

Однак я вважаю наступне рішення набагато простішим, воно дозволяє обмінюватися даними між двома братами (я тестував це лише на 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;
   ...
}

Чи не протистоїть це ідеї нещільного з’єднання і, отже, компонентів?
Робін

Хтось має ідею, чи це чистий чи брудний спосіб? Здається, набагато простіше для обміну даними в одному напрямку, наприклад, лише від sibiling1 до sibiling2, але не навпаки
Сара

9

Тут йде дискусія про це.

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

Відповідь Алекса Дж є хорошою, але вона більше не працює з поточним кутом 4 з липня 2017 року.

І ця посилання на планкер демонструє, як спілкуватися між братами та сестрами, використовуючи спільний сервіс та спостережуваний.

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


7

Директива може мати сенс у певних ситуаціях "підключати" компоненти. Насправді речі, що з'єднуються, навіть не повинні бути повноцінними компонентами, а іноді це легше і насправді простіше, якщо їх немає.

Наприклад, у мене є Youtube Playerкомпонент (обгортання API Youtube), і я хотів декілька кнопок контролера для нього. Єдина причина, що кнопки не є частиною мого основного компонента - це те, що вони розташовані в іншому місці 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 для ProductPage.component, де youtube-player, очевидно, мій компонент, який завершує API Youtube.

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

... lots more DOM ...

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

Директива підключає все до мене, і мені не потрібно оголошувати (натискання) події в HTML.

Таким чином, директива може непогано підключитися до відеоплеєра без участі ProductPageв якості посередника.

Це вперше я це робив, тому ще не знаю, наскільки це може бути масштабним для набагато складніших ситуацій. З цього приводу я щасливий, і це залишає мою HTML простою і обов'язком у всьому.


Однією з найважливіших кутових концепцій, яку слід зрозуміти, є те, що компонент - це лише директива з шаблоном. Коли ви дійсно оціните, що це означає, то директиви не такі вже й страшні - і ви зрозумієте, що можете застосувати їх до будь-якого елемента, щоб прив’язати до нього поведінку.
Simon_Weaver

Я спробував це, але я отримую дублюючу помилку ідентифікатора для еквівалента player. Якщо я залишу першу згадку про гравця, я отримую діапазонError. Я наткнувся, як це має працювати.
Катрін Осборн

@KatharineOsborne Схоже, у моєму фактичному коді я використовую _playerдля приватного поля, яке представляє програвач, так що так, ви отримаєте помилку, якщо скопіювали це саме. Буде оновлено. Вибачте!
Simon_Weaver

4

Ось просте практичне пояснення: тут просто пояснено

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

Компонент, звідки ви хочете зателефонувати спостерігаючим, щоб повідомити інший компонент, що натиснута кнопка

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/


Привіт, велике спасибі за просте рішення> я спробував це в stackblitz, який працював чудово. Але у моїй програмі є ледачі завантажені маршрути (використовували predviin: 'root') та HTTP-дзвінки для встановлення та отримання. Чи можете ви мені допомогти з HTTP-дзвінками. багато пробував, але не працював:
Kshri

4

Спільний сервіс є хорошим рішенням цього питання. Якщо ви також хочете зберегти інформацію про діяльність, ви можете додати спільний сервіс до свого основного списку постачальників модулів (app.module).

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

Тоді ви можете безпосередньо надати його своїм компонентам,

constructor(private sharedService: SharedService)

За допомогою спільної служби ви можете або використовувати функції, або ви можете створити Тему для оновлення декількох місць одночасно.

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

У складі списку ви можете опублікувати інформацію про товар, що натиснув,

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

а потім ви можете отримати цю інформацію у деталі:

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

Очевидно, що дані, які перелічують спільні компоненти, можуть бути будь-якими. Сподіваюся, це допомагає.


Це найпростіший в майбутньому приклад (також короткий) приклад цієї концепції спільної послуги, і її слід реально застосувати для підвищення її видимості, оскільки немає прийнятої відповіді.
iGanja

3

Потрібно встановити відносини батько-дитина між своїми компонентами. Проблема полягає в тому, що ви можете просто вставити дочірні компоненти в конструктор батьківського компонента і зберегти його в локальній змінній. Натомість слід оголосити дочірні компоненти у своєму батьківському компоненті, використовуючи @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.

Але є й інші декларатори властивостей, як пояснено в цій примітці до блогу: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/


2

Предмети поведінки. Я написав блог про це.

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

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

У цьому прикладі я декларую негідну тему поведінки типу номера. Також це спостерігається. І якщо "щось трапиться", це зміниться з новою функцією () {}.

Отже, в компонентах рідних братів один викличе функцію, внести зміни, а на іншу вплине ця зміна, або навпаки.

Наприклад, я отримую ідентифікатор з URL-адреси та оновлюю noid від теми поведінки.

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

ngOnInit(): void { 
 const id = +this.getId ();
 this.taskService.newId(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']); 
              } 
          }) 
    }) 
}

1

Це не те, чого ви точно хочете, але точно вам допоможуть

Я здивований, що там немає більше інформації про комунікацію компонентів <=> розгляньте цей підручник від angualr2

Для зв'язку компонентів братів, я б запропонував перейти 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,[]);

Для отримання додаткового коду зверніться до посилання вгорі.

Редагувати: це дуже невелика демонстрація. Ви вже згадували, що ви вже пробували sharedService. Тому, будь ласка, розгляньте цей підручник від angualr2 для отримання додаткової інформації.


0

Я передавав методи встановлення від батьківського до одного з його дітей через прив'язку, називаючи цей метод даними з дочірнього компонента, це означає, що батьківський компонент оновлюється і потім може оновити свій другий дочірній компонент новими даними. Для цього потрібно прив’язати "це" або використовувати функцію стрілки.

Це має перевагу в тому, що діти не так зв'язані між собою, оскільки їм не потрібна конкретна спільна послуга.

Я не зовсім впевнений, що це найкраща практика, було б цікаво почути погляди інших на це.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.