Угловая 2 родственных компонента связи

85

У меня есть ListComponent. Когда элемент кликается в ListComponent, детали этого элемента должны отображаться в DetailComponent. Оба одновременно отображаются на экране, поэтому нет маршрутизации.

Как указать DetailComponent, какой элемент в ListComponent был нажат?

Я рассмотрел вопрос о выпуске события до родителя (AppComponent), и родительский элемент set selectedItem.id на DetailComponent с @Input. Или я мог бы использовать общую службу с наблюдаемыми подписками.


РЕДАКТИРОВАТЬ: Установка выбранного элемента с помощью события + @Ввод не запускает DetailComponent, хотя, если мне нужно будет выполнить дополнительный код. Поэтому я не уверен, что это приемлемое решение.


Но оба этих метода кажутся гораздо более сложными, чем способ Angular 1 делать то, что было либо через $rootScope. $broadcast или $scope. $parent. $broadcast.

Когда все в Angular 2 является компонентом, я удивляюсь, что там больше информации о связи компонентов.

Есть ли еще один или более простой способ сделать это?

  • 0
    Вы нашли какой-нибудь способ для совместного использования данных? Мне это нужно как можно заметнее ..
Теги:
angular

10 ответов

63

Обновлено до rc.4: При попытке получить данные, переданные между компонентами-братьями, в angular 2, самый простой способ (angular.rc.4) - использовать angular2 иерархическую инъекцию зависимостей и создать общую службу.

Здесь будет служба:

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

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

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

Теперь здесь будет компонент 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{

} 

и его двух детей

ребенок 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 = '';
    }
}

СЕЙЧАС: Что следует учитывать при использовании этого метода.

  • Включить только поставщика услуг для общей службы в компоненте PARENT и NOT для детей.
  • Вам все равно придется включать конструкторы и импортировать службу в дочерних
  • Ответ на этот вопрос был первоначально дан для ранней бета-версии angular 2. Все, что изменилось, это операторы импорта, так что это все, что вам нужно обновить, если вы случайно использовали исходную версию.
  • 0
    Спасибо, но любая идея почему не работает, когда мы добавляем сервис в качестве поставщиков? Я боролся с этим некоторое время и не работал, пока я не удалил поставщиков
  • 2
    Это все еще верно для angular-rc1?
Показать ещё 9 комментариев
23

В случае 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();
  }
}

Источник: Родитель и дети обмениваются данными через службу

  • 1
    Хотели бы вы добавить терминологию к этому ответу. Я считаю, что он соответствует RxJS, наблюдаемой модели и т. Д., Не совсем уверен, но добавление описаний к некоторым из них было бы полезно для людей (таких как я).
9

Здесь обсуждается

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

Ответ Alex J хорош, но он больше не работает с текущей версией Angular 4 по состоянию на июль 2017 года.

И эта плункерная ссылка продемонстрирует, как общаться между братьями и сестрами, используя общий сервис и наблюдаемый.

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

7

Один из способов сделать это - использовать общий сервис.

Однако я считаю, что следующее решение намного проще, оно позволяет обмениваться данными между двумя братьями и сестрами (я проверял это только на 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> 

приложение-sibling2.component.ts

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

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

В определенных ситуациях директива может иметь смысл "соединять" компоненты. На самом деле соединяемые вещи даже не должны быть полными компонентами, а иногда они более легкие и, на самом деле, проще, если это не так.

Например, у меня есть компонент Youtube Player (обёртка YouTube API), и я хотел использовать для него несколько кнопок контроллера. Единственная причина, по которой кнопки не являются частью моего основного компонента, заключается в том, что они расположены в другом месте 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>

Директива подключает все для меня, и мне не нужно объявлять событие (click) в HTML.

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

Это первый раз, когда я на самом деле сделал это, так что пока не уверен, насколько это масштабируемо для гораздо более сложных ситуаций. За это, хотя я счастлив, и это оставляет мой HTML простым и обязанности всего различного.

  • 0
    Одно из самых важных угловых понятий, которое нужно понять, это то, что компонент - это просто директива с шаблоном. Как только вы по-настоящему поймете, что это значит, директивы уже не так страшны - и вы поймете, что можете применить их к любому элементу, чтобы привязать к нему поведение.
  • 0
    Я пробовал это, но я получаю ошибку дублирующего идентификатора для player . Если я пропущу первое упоминание игрока, я получу RangeError. Я озадачен тем, как это должно работать.
Показать ещё 1 комментарий
2

Поведенческие предметы. Я написал блог об этом.

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

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

В этом примере я объявляю субъектом noid-поведение типа номер. И это наблюдаемое. И если "что-то случилось", это изменится с помощью функции new() {}.

Таким образом, в одноуровневых компонентах один вызовет функцию, чтобы внести изменение, а другой будет зависеть от этого изменения, или наоборот.

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

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

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

А с другой стороны, я могу спросить, является ли этот идентификатор "тем, что я хочу", и сделать выбор после этого, в моем случае, если я хочу удалить задачу, а эта задача является текущей ссылкой, она должна перенаправить меня к дому:

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

Вам необходимо настроить отношения родитель-потомок между вашими компонентами. Проблема в том, что вы можете просто вставлять дочерние компоненты в конструктор родительского компонента и хранить его в локальной переменной. Вместо этого вы должны объявить дочерние компоненты в своем родительском компоненте с помощью декларатора свойств @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 hook. Чтобы поймать этот крючок, просто реализуйте интерфейс AfterViewInit в родительском классе таким же образом, как и с OnInit.

Но есть и другие деклараторы свойств, как описано в этом блоге: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/

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

Вот простое практическое объяснение: просто объяснено здесь

В 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 { 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/

0

Я передавал методы setter от родителя к одному из его дочерних элементов через привязку, вызывая этот метод с данными из дочернего компонента, что означает, что родительский компонент обновлен и затем может обновить свой второй дочерний компонент с помощью новые данные. Это требует привязки 'this' или использования функции стрелки.

Это имеет то преимущество, что дети не так связаны друг с другом, поскольку они не нуждаются в конкретной общей службе.

Я не совсем уверен, что это лучшая практика, было бы интересно услышать мнения других по этому поводу.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню