javascript - Angular 4: Suscribiéndose a Observable
Estoy trabajando en un proyecto que requiere que se carguen y seleccionen clientes (no usuarios, sino más bien como clientes).
Desafortunadamente, parece que no puedo suscribirme a los Observables que estoy cargando en mi componente. He probado todas las soluciones posibles que pude encontrar en línea, pero quizás mi inexperiencia con Angular me esté impidiendo encontrar la correcta.
Qué está funcionando en este momento:
- cargar clientes en el cuadro de selección
- dar el valor del id del cliente a la opción en el cuadro de selección
- enviar el id del cliente a client.service y guardar el cliente seleccionado como un Observable
Entonces mi único problema es que el componente no detecta cambios en los Observables en client.service.
Aquí está mi client.service.ts:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { ApiService } from '../api.service';
import { Client } from './client';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
@Injectable()
export class ClientService {
clients$: Observable<Client[]>;
client: Client;
selectedClient$: Observable<Client[]>;
constructor(private api: ApiService) {
this.clients$ = this.getAll();
}
public getAll(): Observable<Client[]> {
return this.api.get<Client[]>('clients');
}
public setSelectedClient(clientId: number) {
this.clients$ = this.getAll();
if (clientId == null) {
// Do nothing
} else {
this.selectedClient$ = this.clients$.map(arr =>
{ return arr.find(client => client.id === clientId) });
}
}
public getSelectedClient() : Observable<Client> {
return this.selectedClient$;
}
}
Mi componente, con partes comentadas para mostrar algunos intentos de solucionar mi problema:
import { Component, OnInit } from '@angular/core';
import { Observable } from "rxjs/Observable";
import { ClientService } from "../client/client.service";
import { Client } from "../client/client";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
@Component({
selector: 'app-left-menu',
templateUrl: './left-menu.component.html',
styleUrls: ['./left-menu.component.css']
})
export class LeftMenuComponent implements OnInit {
selectedClient$ : Observable<Client>;
client: Client = new Client();
clients$ : Observable<Client[]>;
constructor(private clientService: ClientService) {
}
ngOnInit() {
// this.clientService.getSelectedClient().subscribe(selectedClient$ => {
// this.selectedClient$ = Observable.of(selectedClient$);
// });
// //
// this.clientService.getAll().subscribe(clients$ => {
// this.clients$ = Observable.of(clients$);
// });
this.selectedClient$ = this.clientService.getSelectedClient();
this.clients$ = this.clientService.getAll();
}
public setSelectedClient(clientId: number) {
this.clientService.setSelectedClient(clientId);
}
}
Y la parte del HTML que uso para mostrar y seleccionar un cliente:
<select #selectClient [ngModel]="selectedClient$ | async"
(ngModelChange)="setSelectedClient(selectClient.value)">
<option *ngFor="let client of clients$ | async" [value]="client.id">
{{ client.firstName }}
{{ client.preposition }}
{{ client.lastName }}
</option>
</select>
<!-- Displaying selected client -->
<h2 *ngIf="selectedClient$">{{(selectedClient$ | async)?.firstName}}
</h2>
Si alguien pudiera ayudarme con esto, ¡lo agradecería MUCHÍSIMO, gracias!
Respuesta #1
Demostración
usa [ngValue] en lugar de [value], lo que te permite vincular el objeto cliente en sí mismo a la directiva [ngModel], también usé Subject para notificar fácilmente a otros suscriptores sobre el nuevo cliente seleccionado :
cliente.servicio.ts :
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { Client } from './client';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
@Injectable()
export class ClientService {
clients$: Observable<Client[]>;
client: Client;
selectedClient$: Subject<Client> = new Subject<Client>();
//...
public setSelectedClient(client: Client) {
if (client)
this.selectedClient$.next(client);
}
//...
}
component.ts :
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { ClientService } from './client.service';
import { Client } from './client'
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
selectedClient : Client;
client: Client = new Client();
clients$ : Observable<Client[]>;
constructor(private clientService: ClientService) {
}
ngOnInit() {
//...
this.clients$ = this.clientService.getAll();
this.clientService.selectedClient$.subscribe(console.log)
}
setSelectedClient(client : Client){
this.selectedClient = client;
this.clientService.setSelectedClient(client)
}
}
plantilla :
<select #selectClient [ngModel]="selectedClient"
(ngModelChange)="setSelectedClient($event)">
<option *ngFor="let client of clients$ | async" [ngValue]="client">
{{ client.firstName }}
{{ client.preposition }}
{{ client.lastName }}
</option>
</select>
Respuesta #2
Tu problema proviene de la forma en que has asignado las propiedades observables clients$ y selectedClient$ a tu LeftMenuComponent.
El método setSelectedClient() en el ClientService cambiará las propiedades en el servicio. Sin embargo, el ngOnInit() de tu LeftMenuComponent solo realiza la asignación de las propiedades una vez, y los cambios de setSelectedClient() no se reflejarán en el LeftMenuComponent.
Para que esto funcione, no necesitas reasignar las propiedades al componente. Simplemente podrías enlazar las propiedades del servicio directamente dentro de tu plantilla así:
<select #selectClient [ngModel]="clientService.selectedClient$ | async"
(ngModelChange)="setSelectedClient(selectClient.value)">
<option *ngFor="let client of clientService.clients$ | async" [value]="client.id">
{{ client.firstName }}
{{ client.preposition }}
{{ client.lastName }}
</option>
</select>
Otra opción que podría ser más limpia es simplemente usar propiedades getter en el componente en lugar de la asignación, así:
export class LeftMenuComponent implements OnInit {
client: Client = new Client();
get selectedClient$(): Observable<Client> {
return this.clientService.selectedClient$;
}
get clients$(): Observable<Client> {
return this.clientService.client$;
}
constructor(private clientService: ClientService) {
}
ngOnInit() {
// init things, but no assignment needed now
}
