У меня есть ListComponent. Когда элемент кликается в ListComponent, детали этого элемента должны отображаться в DetailComponent. Оба одновременно отображаются на экране, поэтому нет маршрутизации.
Как указать DetailComponent, какой элемент в ListComponent был нажат?
Я рассмотрел вопрос о выпуске события до родителя (AppComponent), и родительский элемент set selectedItem.id на DetailComponent с @Input. Или я мог бы использовать общую службу с наблюдаемыми подписками.
РЕДАКТИРОВАТЬ: Установка выбранного элемента с помощью события + @Ввод не запускает DetailComponent, хотя, если мне нужно будет выполнить дополнительный код. Поэтому я не уверен, что это приемлемое решение.
Но оба этих метода кажутся гораздо более сложными, чем способ Angular 1 делать то, что было либо через $rootScope. $broadcast или $scope. $parent. $broadcast.
Когда все в Angular 2 является компонентом, я удивляюсь, что там больше информации о связи компонентов.
Есть ли еще один или более простой способ сделать это?
Обновлено до 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 = '';
}
}
СЕЙЧАС: Что следует учитывать при использовании этого метода.
В случае 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();
}
}
Здесь обсуждается
https://github.com/angular/angular.io/issues/2663
Ответ Alex J хорош, но он больше не работает с текущей версией Angular 4 по состоянию на июль 2017 года.
И эта плункерная ссылка продемонстрирует, как общаться между братьями и сестрами, используя общий сервис и наблюдаемый.
Один из способов сделать это - использовать общий сервис.
Однако я считаю, что следующее решение намного проще, оно позволяет обмениваться данными между двумя братьями и сестрами (я проверял это только на 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;
...
}
В определенных ситуациях директива может иметь смысл "соединять" компоненты. На самом деле соединяемые вещи даже не должны быть полными компонентами, а иногда они более легкие и, на самом деле, проще, если это не так.
Например, у меня есть компонент 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 простым и обязанности всего различного.
player
. Если я пропущу первое упоминание игрока, я получу RangeError. Я озадачен тем, как это должно работать.
Поведенческие предметы. Я написал блог об этом.
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']);
}
})
})
}
Вам необходимо настроить отношения родитель-потомок между вашими компонентами. Проблема в том, что вы можете просто вставлять дочерние компоненты в конструктор родительского компонента и хранить его в локальной переменной.
Вместо этого вы должны объявить дочерние компоненты в своем родительском компоненте с помощью декларатора свойств @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/
Это не то, что вы точно хотите, но наверняка поможет вам
Я удивлен, что там больше информации о связи компонентов <= > рассмотрите этот учебник 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 для получения дополнительной информации.
Вот простое практическое объяснение: просто объяснено здесь
В 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/
Я передавал методы setter от родителя к одному из его дочерних элементов через привязку, вызывая этот метод с данными из дочернего компонента, что означает, что родительский компонент обновлен и затем может обновить свой второй дочерний компонент с помощью новые данные. Это требует привязки 'this' или использования функции стрелки.
Это имеет то преимущество, что дети не так связаны друг с другом, поскольку они не нуждаются в конкретной общей службе.
Я не совсем уверен, что это лучшая практика, было бы интересно услышать мнения других по этому поводу.