Según tengo entendido, en Angular 2, si desea pasar valores entre componentes no relacionados (es decir, componentes que no comparten una ruta y, por lo tanto, no comparten una relación padre-hijo), lo hace a través de un servicio compartido.

Eso es lo que configuré en mi aplicación Angular2. Estoy verificando para ver si existe cierta serie de caracteres en una url y devolviendo verdadero si es así.

  isRoomRoute(routeUrl) {
      if ((routeUrl.includes('staff') || routeUrl.includes('contractors'))) {
          console.log('This url: ' + routeUrl + ' is a roomRoute');
          return true;
      } else {
          console.log('This url: ' + routeUrl + ' is NOT a room route');
          return false;
      }
  }

En el constructor del componente app.com raíz, me estoy suscribiendo a eventos de enrutamiento:

constructor(private routeService: RouteService,
            private router: Router)  {
    this.router.events.subscribe((route) => {
    let routeUrl = route.url;
    this.routeService.sendRoute(routeUrl);
    this.routeService.isRoomRoute(routeUrl);
    });
}

... y luego usar las URL proporcionadas para verificar si una URL contiene o no la cadena específica. Esto se evalúa cada vez que cambia la ruta.

Así que todo funciona como se esperaba.

Sin embargo, me encuentro con un problema al pasar el resultado de esa verificación a un componente diferente, no relacionado (no padre-hijo).

Aunque estoy usando un servicio compartido (routeService) tanto en el componente app.component como en el componente no relacionado (room.component), lo que funciona en uno no funciona en el otro. Según tengo entendido, la "veracidad" de lo que se verifica aquí debería ser suficiente para devolver una declaración verdadera.

Pero en el componente secundario no relacionado, obtengo un error "indefinido" cuando llamo a la función, así:

  isRoomRoute() {
       if (this.routeService.isRoomRoute(this.routeUrl)) {
           return true;
       }
     }

Entonces aquí es donde estoy atrapado. Básicamente, la evaluación de si una url contiene una cadena determinada ya ha sucedido. Ahora solo necesito pasar el resultado booleano de esa verificación al componente secundario no relacionado. ¿Cómo puedo hacer esto mejor en Angular 2?

5
Muirik 11 ene. 2017 a las 02:27

4 respuestas

La mejor respuesta

Su comprensión es correcta, un servicio compartido injectable es una forma común de comunicación entre múltiples componentes no relacionados.

Aquí está el recorrido de tal caso de uso.

En primer lugar, según su situación, escucharemos los eventos Router en AppComponent, obtendremos la ruta activa y la pasaremos a RouteService para que el servicio pueda manipularla y / o servirla a otros componentes.

Así es como debería ser AppComponent:

export class AppComponent {

    constructor(private _router: Router,
                private _routeService: RouteService) {

        this._router.events.subscribe(event => {
            if (event instanceof NavigationEnd) {
                let url = event.urlAfterRedirects;
                this._routeService.onActiveRouteChanged(url);
            }
        });
    }

}

Cuando se trata del servicio, aquí presentaremos el BehaviorSubject como delegado, para que los componentes que usan el servicio puedan suscribirse a los cambios de datos del servicio. Para obtener más información sobre BehaviorSubject y otros temas, visite: Delegación: EventEmitter u Observable en Angular2

Aquí está la implementación de nuestro RouteService compartido (los componentes deben usa la instancia única del servicio, así que asegúrate de haberlo proporcionado en el nivel raíz):

@Injectable()
export class RouteService {

    isRoomRouteSource: BehaviorSubject<boolean> = new BehaviorSubject(false);

    constructor() { }

    onActiveRouteChanged(url: string): void {
        let isRoomRoute = this._isRoomRoute(url);
        this.isRoomRouteSource.next(isRoomRoute);
        // do other stuff needed when route changes
    }

    private _isRoomRoute(url: string): boolean {
        return url.includes('staff') || url.includes('contractors');
    }
}

El ejemplo de otro componente que usa el servicio y se suscribe a nuestros BehaviorSubject cambios:

export class AnotherComponent {

    isCurrentRouteRoomRoute: boolean;

    constructor(private _routeService: RouteService) {
        this._routeService.isRoomRouteSource.subscribe((isRoomRoute: boolean) => {
            this.isCurrentRouteRoomRoute = isRoomRoute;
            // prints whenever active route changes
            console.log('Current route is room route: ', isRoomRoute);
        });
     }

}

Si no es necesario suscribirse a los cambios isRoomRouteSource, digamos que solo necesitamos el último valor almacenado, luego:

export class AnotherComponent {

    isCurrentRouteRoomRoute: boolean;

    constructor(private _routeService: RouteService) {
        this.isCurrentRouteRoomRoute = this._routeService.isRoomRouteSource.getValue(); // returns last value stored
        console.log('Current route is room route: ', this.isCurrentRouteRoomRoute);
     }

}

Espero que esto haya ayudado!

6
Community 23 may. 2017 a las 11:47

Solo mirando su código parece que algo es incorrecto aquí.

  isRoomRoute() {
       if (this.routeService.isRoomRoute(this.routeUrl)) {
           return true;
       }
     }
this.routeUrl
@Injectable()
class routeService {
  constructor(private router: Router) {
    // subscribe to event
    router.subscribe((url) => {
      this.routeUrl = url;
      // other things?  sendRoute??
    });

  }

  // Other methods for this class
  isRoomRoute() {
    return this.routeUrl && (this.routeUrl.includes('staff') || this.routeUrl.includes('contractors'));
  }
}

// Usage later where this service has been injected
@Component({
 // ... other properties
 providers: [routeService]
})
class someComponent {
  constructor(private routeService: routeService) {}
  someMethod() {
    this.routeService.isRoomRoute();  // Check if we are in a room route.
  }
}

En un caso como este, no estoy seguro de por qué no puede simplemente obtener la URL y analizarla cuando se llama a RoomRoute en lugar de configurar algo en los eventos de enrutamiento.

1
Goblinlord 12 ene. 2017 a las 12:53

(Se me pidió que publicara una muestra de lo que estaba hablando en los comentarios):

Pienso en eventos angulares prácticos / flujo de datos de esta manera:

  • "Interesado" escucha el evento emitido desde el EventEmitter de un componente (porque tiene una referencia y está suscrito a esa referencia).
  • Algo emite un par a través de un EventEmitter, y cualquier cosa con referencia a él y suscrito, lo escuchará.

Todos lo hacen con EventEmitters. Entonces, un padre puede conectarse al emisor de eventos de un niño, y un niño puede conectarse al de un padre. Todos pueden conectarse al Servicio. Pero también, todo es hijo de una "aplicación". Por lo tanto, al final (aunque un Servicio sería el camino a seguir), puede construir un sistema de comunicación de componentes completo simplemente haciendo que cada componente se enganche en el emisor de eventos de la "aplicación".

Esta muestra es una forma de abordar el problema de comunicación del botón de la barra de menú: cuando se hace clic en un botón, desea que todos lo sepan (para que pueda resaltar el fondo seleccionado y resaltar el resto, lo que sea). Los componentes del botón son pares, relacionados solo porque tienen un nivel superior superior.

Entonces, en el componente principal (que puede ser tan limitado como un MenuBarComponent o tan amplio como "app"):

<ul>
  <li *ngFor="let btn of buttons">
    // Bind the parent's broadcast listener, to the child Input. 
    <my-child [broadcastListener]="broadcasterParent">
    </my-child>
  </li>
</ul>

Aquí el padre le está dando al niño, a través de una Entrada, una referencia a su EventEmitter (el padre) (difusión y tales son solo nombres típicos). Entonces, cuando el padre emite un evento desde su EventEmitter, o cualquiera de los niños usa la referencia a ese emisor para emitir un evento, todos los componentes con una referencia a través de una Entrada, y que se han suscrito a esa referencia, lo escucharán.

El código principal detrás de esa plantilla anterior (tenga en cuenta que uso ES6, estoy bastante seguro de que obtendrá la idea, y solo uso constructores para mantenerlo corto):

import { Component, EventEmitter } from '@angular/core';
...
constructor  ) {
  // this is the instance given to the child as Input
  this.broadcasterParent = new EventEmitter ( );
}

En la niña:

import { Component } from '@angular/core';
...
  constructor ( ) {
    // This is almost certainly better used in OnInit.
    // It is how the child subscribes to the parent's event emitter.
    this.broadcastListener.subscribe( ( b ) => this.onBroadcast ( b ) );
  }

  onButtonClick ( evt ) {
    // Any component anywhere with a ref to this emitter will hear it
    this.broadcastListener.emit ( evt );
  }

  onBroadcast ( evt ) {
    // This will be the data that was sent by whatever button was clicked
    // via the subscription to the Input event emitter (parent, app, etc).
    console.log ( evt );        
}

ChildComponent.annotations = [
  new Component ( {
      ... // TS would use an attribute for this
      inputs: [ 'broadcastListener' ]
      template: `
        <div click)="onButtonClick ( $event )">
           ...
        </div>
      `
  })
];

Un servicio realmente hace más o menos lo mismo, pero un servicio está "flotando" y tiene acceso mediante inyección, en lugar de estar fijo en la jerarquía y acceder a través de Entrada (y puede hacerlo más robusto, etc.).

Por lo tanto, cualquier botón en el que se haga clic emitirá un evento que todos escucharán porque todos están suscritos al mismo emisor de eventos (ya sea en un Servicio o lo que sea).

0
Tim Consolazio 11 ene. 2017 a las 02:05

Dije que lo haría, así que aquí está la misma idea que mi primera respuesta, simplemente usando un servicio "flotante" (por lo que no es cosa de padre / hijo). Aunque ingenuo, este es el quid de la cuestión.

Primero, cree el servicio EmitService.

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

@Injectable()
export class EmitService {

  private _emitter;

  constructor() {
    this._emitter = new EventEmitter ( );
  }

  subscribeToServiceEmitter ( callback ) {
    return this._emitter.subscribe ( b => callback ( b ) );
  }

  emitThisData ( data ) {
    this._emitter.emit ( data );
  }
}

Cree dos componentes, pueden estar en cualquier lugar de la aplicación. Aquí está CompOneComponent, cópielo para crear CompTwoComponent:

import { Component, OnInit, OnDestroy } from '@angular/core';
// the CLI puts components in their own folders, adjust this import
// depending on your app structure...
import { EmitService } from '../emit.service';

@Component({
  selector: 'app-comp-one',
  templateUrl: './comp-one.component.html',
  styleUrls: ['./comp-one.component.css']
})
export class CompOneComponent implements OnInit, OnDestroy {

  private _sub;

  constructor( private _emitSvc : EmitService ) {}

  ngOnInit() {
    // A subscription returns an object you can use to unsubscribe
    this._sub = this._emitSvc.subscribeToServiceEmitter ( this.onEmittedEvent );

    // Watch the browser, you'll see the traces. 
    // Give CompTwoComponent a different interval, like 5000
    setTimeout( () => {
      this._emitSvc.emitThisData ( 'DATA FROM COMPONENT_1' );
    }, 2000 );

  }

  onEmittedEvent ( data ) {
    console.log ( `Component One Received ${data}` );
  };

  ngOnDestroy ( ) {
    // Clean up or the emitter's callback reference
    this._sub.unsubscribe ( );
  }
}

Añádelo todo a tu aplicación; los componentes son todos de nivel superior aquí, pero no tienen que serlo, pueden existir en cualquier lugar:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { CompOneComponent } from './comp-one/comp-one.component';
import { CompTwoComponent } from './comp-two/comp-two.component';

import { EmitService } from './emit.service';

@NgModule({
  declarations: [
    AppComponent,
    CompOneComponent,
    CompTwoComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  exports: [ ],
  providers: [ EmitService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

Y eso es. Ahora ambos componentes tienen acceso al Servicio (y se han suscrito a su EventEmitter), por lo que pueden decirle que emita eventos y recibirán cualquier evento que se active por cualquier otro componente. Cree una aplicación CLI, agregue estas cosas, ejecútelas y verá que la consola se dispara como se espera (tenga en cuenta que el componente que emite el evento también lo escuchará, puede filtrarlo de dos maneras diferentes ) Cualquier cosa en la que pueda inyectar el EmitService puede usarlo independientemente de "dónde" esté.

0
Tim Consolazio 12 ene. 2017 a las 14:25