CS_student
CS_student

Reputation: 107

Angular 4: Subscribing to Observable

I am working on a project that required clients (not users but more like customers) to be loaded and selected.

Unfortunately I can't seem to subscribe to the Observables I am loading in my component. I have tried every possible solution I could find online but perhaps my inexperience with Angular is preventing me from finding the right one.

What is working right now:
- loading clients in select box
- giving value of client' id to option in select box
- sending client's id to client.service and saving the selected client as an Observable

So my only problem is that the component doesn't notice change in the Observables in client.service.

Here is my 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$;
    }

}

My component, with commented bits to show some attempts at fixing my problem:

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);
    }

}

And the part of html I use to display and select a client:

 <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>

If anyone could help me out with this it would be MUCH appreciated, thanks!

Upvotes: 2

Views: 6473

Answers (2)

El houcine bougarfaoui
El houcine bougarfaoui

Reputation: 37343

Demo

use [ngValue] instead of [value] which let you bind the client object itself to the [ngModel] directive, I also used Subject to easily notify other subscribers to the new selected client :

client.service.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)
    }
}

template :

<select #selectClient [ngModel]="selectedClient" 
(ngModelChange)="setSelectedClient($event)">
     <option *ngFor="let client of clients$ | async" [ngValue]="client">
         {{ client.firstName }}
         {{ client.preposition }}
         {{ client.lastName }}
     </option>
</select>

Upvotes: 1

Joshua Skrzypek
Joshua Skrzypek

Reputation: 458

Your problem comes from the way that you have assigned the clients$ and selectedClient$ observable properties to your LeftMenuComponent.

The setSelectedClient() method on the ClientService will change the properties on the service. However, your LeftMenuComponent's ngOnInit() only does the assignment of the properties once, and the changes from setSelectedClient() won't be reflected on the LeftMenuComponent.

To make this work, you don't need to re-assign the properties onto to component. You could simply bind the properties of the service directly inside your template like this:

 <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>

Another option that might be cleaner is to simply use getter properties on the component instead of the assignment, like this:

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
    }

Upvotes: 0

Related Questions