Exploring Angular 4: Embracing the Power of Observables

I am currently working on a project that involves loading and selecting clients (not users, but more like customers).

However, I have encountered an issue where I am unable to subscribe to the Observables being loaded in my component. Despite trying various solutions found online, my lack of experience with Angular seems to be hindering me from finding the right solution.

What is functioning correctly at the moment:
- Loading clients in a select box
- Assigning the client's ID value to the options in the select box
- Sending the client's ID to client.service and saving the selected client as an Observable

My only challenge lies in the fact that the component fails to recognize changes in the Observables within the client.service.

Below is the code snippet for 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$;
    }

}

This is the section of my component showing attempts made to resolve the issue:


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.selectedClient$ = this.clientService.getSelectedClient();
        this.clients$ = this.clientService.getAll();
    }

    public setSelectedClient(clientId: number) {
        this.clientService.setSelectedClient(clientId);
    }

}

Lastly, included below is the HTML segment used 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>

Your assistance in resolving this issue would be greatly appreciated. Thank you!

Answer №1

Experience the Demo

Instead of using [value], try utilizing [ngValue]. This allows you to directly bind the client object to the [ngModel] directive. Additionally, I've incorporated the use of the Subject to easily inform other subscribers about the newly 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>

Answer №2

The issue you are facing stems from how you have linked the observable properties clients$ and selectedClient$ to your LeftMenuComponent.

When the setSelectedClient() method in the ClientService is used to modify the properties, the changes do not reflect in the LeftMenuComponent as its ngOnInit() only assigns the properties once.

To resolve this, instead of re-assigning the properties within the component, you can directly bind the service properties in the template like so:

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

Alternatively, a neater approach would be utilizing getter properties in the component instead of direct assignment:

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() {
        // perform initialization without explicit property assignment
    }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Enhance your Primeng split button with an added icon when selected

Is it possible to add a tick icon when the user selects the click option in a split button? Any advice on how to achieve this would be appreciated. Thank you. For example, similar to the image below: https://i.stack.imgur.com/owOgE.png ...

Dealing with the challenge of sorting and editing one div at a time

I'm encountering an issue with my script where the input textbox becomes uneditable when I use sortable for a div element. In my code, I have drag and drop functionality where after dropping, the div should be sortable and the input should be editabl ...

Different time parameter for setting a timeout in Javascript

I am struggling to create a slideshow with varying time intervals for each image. My knowledge of Javascript is limited, and I have been working on resolving this issue for quite some time. /* function ShowEffect(element){ new Effect.Appear(element, ...

What causes observables stream to be cancelled or stopped by my HTTP request?

I am facing an issue with adding a new property called Blobs to each application in an array of applications. I need to make a separate HTTP request to fetch Blobs for each application using the switchMap operator. However, the problem is that only the las ...

Validating React Typescript Props: Ensuring that two specific props do not exist simultaneously

Currently, I'm developing a reusable component in React-Typescript and I am looking to validate my props OnClick and component as follows: Both onClick and component prop are optional. These props will only be passed to the component if they need to ...

How can the ordering of dynamically generated components be synchronized with the order of other components?

Currently, I'm delving into Vue 3 and encountering a specific issue. The tabs library I'm using only provides tab headers without content panels. To work around this limitation, I've come up with the following solution: <myTabs/><!- ...

How can I safeguard my app from crashing when the initial state is set by undefined props?

What is the best way to prevent the state from crashing if props native_languages[0] does not exist? This is my attempt at protecting the app, but it still crashes when native_languages[0] does not exist: this.state = { language: props.currentDocu ...

Determine if Toggle Accordion is Expanded by Checking HTML Attribute Using Selenium

I am working with an HTML Angular Material Accordion component. When it is expanded, the class mat-expanded is added. <mat-expansion-panel _ngcontent-ng-cli-universal-c931="" data-qa="product" class="mat-expansion-panel ng- ...

Error in React Typescript: No suitable index signature with parameter type 'string' was located on the specified type

I have encountered an issue while trying to dynamically add and remove form fields, particularly in assigning a value for an object property. The error message I received is as follows: Element implicitly has an 'any' type because expression o ...

Should the .js file type be omitted in the <script> tag for semantic accuracy?

Upon reviewing some inherited code, I noticed that JavaScript files are being linked in the following format: <script src="/js/scriptfile" type="text/javascript"></script> However, I was expecting to see: <script src="/js/scriptfile.js" ...

After the initial submission, the Bootstrap placeholder for the Select Option is only displayed

I'm attempting to create a placeholder for my dropdown with multiple options using Angular and Bootstrap. However, it seems like I may be doing something incorrectly. I created an option for a placeholder such as please select and placed it at the top ...

Utilize an image in place of text (script type="text/javascript")

The vendor has provided me with some code: <a class="sh_lead_button" href="https://107617.17hats.com/p#/lcf/sfrnrskrvhcncwvnrtwwvhxvzkrvzhsd" onclick="shLeadFormPopup.openForm(event)">FREE Puppies</a> <script type="text/javascript" src="htt ...

The Material Table in Angular is having issues with sorting functionality

I tried implementing the basic example from the angular material website, which displays a table with accurate data but the sorting functionality is not working as expected. For reference, you can view the StackBlitz demo here: https://stackblitz.com/edit ...

Upon a successful AJAX post request, the page fails to display

I'm encountering an issue connecting my front-end JavaScript to my back-end Node/Express. Although the requests from my client-side js to the server return successful, the page I am requesting fails to render. On the server side, here is my code: ap ...

Concealing a child component when hovering over its parent element with styled-components

I have a react component structured like this - const MyComponent = () => ( <ContainerSection> <DeleteButtonContainer> <Button theme="plain" autoWidth onClick={() = ...

Ways to invoke a function in an angular component from a separate component located in a different .ts file

File3.ts export class3(){ method1(x,y){ .... } } File4.ts export class4(){ a: string = "abc" b: string ="xyz" //How can I call method1 and pass parameters from file 3? method1(x,y); } I attempted the following in Fi ...

What is the mechanism by which the useState hook in React determines the calling context?

After transitioning from using class components to functional components in React, I delved into the documentation with keen interest to understand how the useState hook functions. Upon consulting the FAQ page, it was explained that each component has an ...

Tips for displaying an error message when there is no match found in ng repeat due to filtering

I'm facing an issue with displaying an error message when no match is found after searching in a text box. I've used ng-show to display the message, but it's not working as expected. Can someone assist me with this problem? I am relatively n ...

Ways to attain a similar format - input map

After pressing the save button, I am aiming to achieve the following effect: "timeTable":{ "0": [{"from":"08:00","to":"12:00"}, {"from":"14:00","to":"18:20&q ...

Tips for incorporating a JavaScript script into local HTML code

I am facing an issue with my code on jsfiddle. It works perfectly there, but when I try to run it locally, it doesn't seem to work. I have checked the code multiple times and even downloaded the jQuery file to link it, but still no luck. I feel like i ...