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

The selected attribute does not function properly with the <option> tag in Angular

Currently, I am faced with a challenge involving dropdowns and select2. My task is to edit a product, which includes selecting a category that corresponds to the product. However, when I open the modal, the selected group/category is not displayed in the d ...

Inconsistent CSS3 transitions behavior in Firefox 4

I have been playing around with some css3 transitions and created a slider test. It works perfectly in webkit browsers, but I have noticed an issue in Firefox 4. When clicking on the left link for the first time, the slider is supposed to slide to the left ...

ngRepeat does not completely refresh the DOM when dealing with a basic array

I have a simple array of numbers shown below using ng-repeat: n = [1,2,3,4,5,6] The problem arises when I modify this array, for example: n=[1,2,3] Instead of fully reloading the DOM, only the last 3 div elements corresponding to array 4, 5, 6 are remo ...

Is it necessary for me to master React in order to code with React Native?

As I move on to learning React and/or React Native after mastering Angular, it feels like a natural progression in my development journey. My understanding is that React Native could streamline the process of building Android/iOS native apps within one pr ...

Triggering a gTag Event on the Fly using Google Tag Manager

I implemented a script that triggers a dynamic gTag.js AdWords conversion based on various user interactions on my webpage. Everything was working smoothly until I switched to Google Tag Manager. Now, the code snippet: gtag('event', 'convers ...

Using AngularJs, you can access the document.body.onfocus event within the controller of

I am attempting to detect when the user closes or cancels the File Upload Window <input type="file"> Since there isn't a built-in listener for the close event of the file upload, I am trying to capture it using the document.body.focus event, s ...

managing nested JSON arrays in JavaScript

I have a straightforward task with handling a simple array that is divided into two parts: a group of vid_ids and a single element named page. Initially, I was iterating through the vid_id array using a for loop. However, upon adding the page element, I en ...

Effortlessly moving through each day on Fullcalendar by utilizing the resourceTimeline feature

I have been using the Fullcalendar plugin and I am wondering if there is a way to navigate from day to day instead of in 3-day increments when using the resourceTimeline view with a duration set to 3 days. Thank you calendar = new FullCalendar.Calendar(ca ...

"Implementing AngularJS bidirectional data binding to dynamically link user inputs with corresponding fields

Having trouble automatically outputting data with angularJS. One of the great features of angular is two-way data binding, but I can't seem to bind input with a JSON file. What I want to achieve is if the user's input matches a key, the correspon ...

How can I retrieve the total number of records (count) in an XML response using PostMan?

Hello, I'm currently attempting to determine the length of an XML response but I'm running into some issues. The error message I am encountering is as follows: "There was an error in evaluating the test script: ReferenceError: xml2json is not def ...

The page reloads automatically following the completion of an ajax request

Hey there, I have a basic form with just a text field. When we submit the form, the data entered in the text field gets stored in the database through ajax. Although the ajax function is working properly and the data is being submitted, the page automatica ...

How to Use AJAX, jQuery, and JSON to Send an Array to PHP

I'm attempting to send an associative array through AJAX $.post to a PHP script. Below is the code I am using: var request = { action: "add", requestor: req_id, ... } var reqDetails = $("#request_details").val(); ...

Once the page is refreshed, the checkbox should remain in its current state and

I have a challenge with disabling all checkboxes on my page using Angular-Js and JQuery. After clicking on a checkbox, I want to disable all checkboxes but preserve their state after reloading the page. Here is an example of the code snippet: $('# ...

Leveraging webpack for requiring modules in the browser

I've been attempting to utilize require('modules') in the browser with webpack for the past couple of days, but I could achieve the same functionality with browserify in just 5 minutes... Below is my custom webpack.config.js file: var webp ...

I am currently working on building a single-page application using React and Vite. However, I am facing an issue where the page is not rendering anything and just displaying a blank screen. Can anyone provide guidance on troubleshooting and

I am currently facing an issue while trying to build a react website using Vite. No matter what I do, nothing seems to render on the page. I even tried removing the react-router-dom and directly rendering the Home file, but still no luck. It appears that i ...

Deploying NextJs application with Firebase's static site generation (SS

TL;DR The new data I add to my project is not displaying on the site, even though it's in the database. The issue arises after deploying with Firebase. I created a meetup website using Firebase as the backend. Everything works fine in development mo ...

What is the process for accessing a URL using a web browser and receiving a plain text file instead of HTML code?

Hello there! I've created a simple HTML file located at that can display your public IP address. If you take a look at the code of the page, you'll notice that it's just plain HTML - nothing fancy! However, I'm aiming for something mo ...

Track every click on any hyperlink throughout the entire webpage

I am attempting to capture any click event on a link throughout the entire page. For example, when a user clicks on the following link: <a href="/abc">abc<a/> I want to be able to retrieve the anchor tag like so: <span hre="/abc">abc& ...

What is the process for retrieving information from my Google Analytics account to incorporate into my website?

Imagine being the proud owner of Your website is equipped with a Google Analytics script that diligently gathers data about your valuable visitors. Now, you have a desire to set up a page views counter. How can you extract data from your own account? ...

Issue with populating labels in c3.js chart when loading dynamic JSON data

Received data from the database can vary in quantity, ranging from 3 to 5 items. Initially, a multi-dimensional array was used to load the data. However, when the number of items changes, such as dropping to 4, 3, 2, or even 1, the bars do not populate acc ...