javascript - ejemplo - Comunicación de componente angular de 2 hermanos
use javascript in angular 2 (11)
Tengo un ListComponent. Cuando se hace clic en un elemento en ListComponent, los detalles de ese elemento deben mostrarse en DetailComponent. Ambos están en la pantalla al mismo tiempo, por lo que no hay enrutamiento involucrado.
¿Cómo le digo a DetailComponent en qué elemento de ListComponent se hizo clic?
He considerado emitir un evento hasta el padre (AppComponent), y tengo que el padre establezca el SelectedItem.id en DetailComponent con un @Input. O podría usar un servicio compartido con suscripciones observables.
EDITAR: Sin embargo, configurar el elemento seleccionado a través del evento + @Input no activa el DetailComponent, en caso de que necesite ejecutar código adicional. Así que no estoy seguro de que sea una solución aceptable.
Pero ambos métodos parecen mucho más complejos que la forma Angular 1 de hacer las cosas, que era a través de $ rootScope. $ Broadcast o $ scope. $ Parent. $ Broadcast.
Dado que todo en Angular 2 es un componente, me sorprende que no haya más información sobre comunicación de componentes.
¿Hay otra forma más directa de lograr esto?
Aquí hay una explicación práctica simple: simplemente explicada here
En 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();
}
}
Componente desde donde desea llamar observable para informar a otro componente que se hace clic en el botón
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'');
}
}
Componente donde desea realizar una acción en el botón en el que se hizo clic en otro componente
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
});
}
Mi comprensión clara sobre la comunicación de componentes al leer esto: here
Debe configurar la relación padre-hijo entre sus componentes.
El problema es que simplemente puede inyectar los componentes secundarios en el constructor del componente principal y almacenarlos en una variable local.
En su lugar, debe declarar los componentes secundarios en su componente primario utilizando el
@ViewChild
propiedades
@ViewChild
.
Así es como debería verse su componente principal:
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
Tenga cuidado, el componente hijo no estará disponible en el constructor del componente padre, justo después de que se
ngAfterViewInit
gancho del ciclo de vida
ngAfterViewInit
.
Para atrapar este gancho, simplemente implemente la interfaz
AfterViewInit
en su clase principal de la misma manera que lo haría con
OnInit
.
Pero, hay otros declaradores de propiedades como se explica en esta nota de blog: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/
El servicio compartido es una buena solución para este problema. Si también desea almacenar información sobre la actividad, puede agregar el Servicio compartido a la lista de proveedores de sus módulos principales (app.module).
@NgModule({
imports: [
...
],
bootstrap: [
AppComponent
],
declarations: [
AppComponent,
],
providers: [
SharedService,
...
]
});
Luego puede proporcionarlo directamente a sus componentes,
constructor(private sharedService: SharedService)
Con el Servicio compartido, puede usar funciones o puede crear un Asunto para actualizar varios lugares a la vez.
@Injectable()
export class FolderTagService {
public clickedItemInformation: Subject<string> = new Subject();
}
En el componente de la lista, puede publicar información sobre elementos en los que se hizo clic
this.sharedService.clikedItemInformation.next("something");
y luego puede obtener esta información en su componente de detalle:
this.sharedService.clikedItemInformation.subscribe((information) => {
// do something
});
Obviamente, los datos que enumeran los componentes compartidos pueden ser cualquier cosa. Espero que esto ayude.
En el caso de 2 componentes diferentes (componentes no anidados, padre / hijo / nieto), le sugiero esto:
Servicio de misión:
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);
}
}
Astronauta Componente:
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();
}
}
Hay una discusión al respecto aquí.
https://github.com/angular/angular.io/issues/2663
La respuesta de Alex J es buena, pero ya no funciona con Angular 4 actual a partir de julio de 2017.
Y este enlace plunker demostraría cómo comunicarse entre hermanos utilizando un servicio compartido y observable.
He estado pasando métodos de establecimiento del padre a uno de sus hijos a través de un enlace, llamando a ese método con los datos del componente hijo, lo que significa que el componente padre se actualiza y luego puede actualizar su segundo componente hijo con los nuevos datos. Sin embargo, requiere vincular ''esto'' o usar una función de flecha.
Esto tiene el beneficio de que los niños no están tan unidos entre sí, ya que no necesitan un servicio compartido específico.
No estoy del todo seguro de que esta sea la mejor práctica, sería interesante escuchar las opiniones de otros sobre esto.
Una directiva puede tener sentido en ciertas situaciones para ''conectar'' componentes. De hecho, las cosas que se están conectando ni siquiera necesitan ser componentes completos, y a veces es más liviano y más simple si no lo son.
Por ejemplo, tengo un componente del
Youtube Player
(envolviendo la API de Youtube) y quería algunos botones de controlador para él.
La única razón por la que los botones no son parte de mi componente principal es porque están ubicados en otra parte del DOM.
En este caso, en realidad es solo un componente de ''extensión'' que solo se utilizará alguna vez con el componente ''principal''. Digo ''padre'', pero en el DOM es un hermano, así que llámalo como quieras.
Como dije, ni siquiera necesita ser un componente completo, en mi caso es solo un
<button>
(pero podría ser un componente).
@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
}
}
En el HTML para
ProductPage.component
, donde
youtube-player
es obviamente mi componente que envuelve la API de Youtube.
<youtube-player #technologyVideo videoId=''NuU74nesR5A''></youtube-player>
... lots more DOM ...
<button class="play-button"
ytPlayerPlayButton
[ytPlayerVideo]="technologyVideo">Play</button>
La directiva me conecta todo, y no tengo que declarar el evento (clic) en el HTML.
Por lo tanto, la directiva se puede conectar muy bien al reproductor de video sin tener que involucrar a
ProductPage
como mediador.
Esta es la primera vez que lo hago, así que aún no estoy seguro de cuán escalable podría ser para situaciones mucho más complejas. Sin embargo, por esto estoy contento y deja mi HTML simple y las responsabilidades de todo lo distinto.
Una forma de hacerlo es usar un servicio compartido .
Sin embargo, la siguiente solución me parece mucho más simple: permite compartir datos entre 2 hermanos (probé esto solo en Angular 5 )
En su plantilla de componente principal:
<!-- 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;
...
}
Actualizado a rc.4: cuando se intenta pasar datos entre componentes hermanos en angular 2, la forma más simple en este momento (angular.rc.4) es aprovechar la inyección de dependencia jerárquica de angular2 y crear un servicio compartido.
Aquí estaría el servicio:
import {Injectable} from ''@angular/core'';
@Injectable()
export class SharedService {
dataArray: string[] = [];
insertData(data: string){
this.dataArray.unshift(data);
}
}
Ahora, aquí estaría el componente PADRE
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{
}
y sus dos hijos
niño 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;
}
}
niño 2 (es hermano)
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 = '''';
}
}
AHORA: Cosas a tener en cuenta al usar este método.
- Incluya solo el proveedor de servicios para el servicio compartido en el componente PADRE y NO los hijos.
- Aún debe incluir constructores e importar el servicio en los elementos secundarios.
- Esta respuesta se respondió originalmente para una versión beta angular 2 temprana. Sin embargo, todo lo que ha cambiado son las declaraciones de importación, por lo que es todo lo que necesita actualizar si utilizó la versión original por casualidad.
Sujetos de comportamiento. Escribí un blog sobre eso.
import { BehaviorSubject } from ''rxjs/BehaviorSubject'';
private noId = new BehaviorSubject<number>(0);
defaultId = this.noId.asObservable();
newId(urlId) {
this.noId.next(urlId);
}
En este ejemplo, estoy declarando un tema de comportamiento de noid de número de tipo. También es un observable. Y si "algo sucedió" esto cambiará con la nueva función () {}.
Entonces, en los componentes del hermano, uno llamará a la función, para hacer el cambio, y el otro se verá afectado por ese cambio, o viceversa.
Por ejemplo, obtengo la identificación de la URL y actualizo el noid del sujeto de comportamiento.
public getId () {
const id = +this.route.snapshot.paramMap.get(''id'');
return id;
}
ngOnInit(): void {
const id = +this.getId ();
this.taskService.newId(id)
}
Y desde el otro lado, puedo preguntar si esa identificación es "lo que quiera" y tomar una decisión después de eso, en mi caso, si quiero eliminar una tarea, y esa tarea es la url actual, debe redirigirme a la casa:
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'']);
}
})
})
}
Esto no es exactamente lo que quieres, pero seguro que te ayudará
Me sorprende que no haya más información sobre comunicación de componentes <=> considere este tutorial de angualr2
Para la comunicación de componentes hermanos, sugeriría ir con
sharedService
.
Sin embargo, también hay otras opciones disponibles.
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,[]);
Consulte el enlace en la parte superior para obtener más código.
Editar:
Esta es una demostración muy pequeña.
Ya mencionó que ya lo intentó con
sharedService
.
Por lo tanto,
considere este tutorial de angualr2
para obtener más información.