What is the best way to effectively use combinedLatestWith?

https://stackblitz.com/edit/angular-ivy-s2ujmr?file=src/app/country-card/country-card.component.html

I am currently working on implementing a search bar in Angular that filters the "countries$" Observable based on user input.


My approach involves creating two additional Observables: one for the search term and another for loading flags. I plan to use "combinedLatestWith" to filter flags whenever there is new input in the search term.

As a newcomer to Angular, I have not used "combinedLatestWith" before and I am facing difficulties in making it work. Below is the code snippet I have been working on:


/* The JavaScript code provided here */

However, I am encountering TypeScript errors:

Property 'pipe' does not exist on type 'OperatorFunction<unknown, [unknown, unknown, string | null]>'.ts(2339)

+

Property 'filter' does not exist on type 'HomeComponent'.ts(2339)


I would appreciate assistance in resolving these errors. Additionally, I seek guidance on whether my approach is correct. If I manage to fix the TypeScript errors, will "combinedLatest" function as intended to filter the results? Am I utilizing "combinedLatestWith" correctly?

Answer №1

Hey @Eric Andre, have you considered using the combineLatestWith method in your code? Adding a filter pipe could also be beneficial. Check out this StackBlitz for more information:

StackBlitz Link

import { Pipe, PipeTransform } from '@angular/core';
            
@Pipe({name: 'filterCountries'})
export class FilterCountriesPipe implements PipeTransform {
   transform(items, input: string): any {
      if (!items || !input || !input.replace(/[\n\r\s\t]+/g, '')) {
         return items;
      }
      return items.filter((item) => item.name.official.toLowerCase().includes(input.toLowerCase()));
 }
}

Check out the HomeComponent template:

<input placeholder="Filter Countries" [(ngModel)]="inputValue" type="text" />
<app-country-card *ngFor="let country of countries$ | async | filterCountries: inputValue" [country]="country"></app-country-card>

Here is the HomeComponent component:

import { Component, OnInit } from '@angular/core';
import { ApiService } from '../../api.service';
    
    @Component({
      selector: 'app-home',
      templateUrl: './home.component.html',
      styleUrls: ['./home.component.css'],
    })
    export class HomeComponent implements OnInit {
      countries$: any;
      inputValue: string;
      constructor(private api: ApiService) {}
    
      ngOnInit() {
        this.countries$ = this.api.getAllCountries();
      }
    }`

And lastly, the AppModule:

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

import { AppRoutingModule } from './app-routing/app-routing.module';
import { AppComponent } from './app.component';
import { CountryCardComponent } from './country-card/country-card.component';
import { HomeComponent } from './pages/home/home.component';
import { HttpClientModule } from '@angular/common/http';
import { FilterCountriesPipe } from './pipes/filter-countries.pipe';

@NgModule({
  declarations: [
    AppComponent,
    CountryCardComponent,
    HomeComponent,
    FilterCountriesPipe,
  ],
  imports: [BrowserModule, AppRoutingModule, HttpClientModule, FormsModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Answer №2

Utilizing combineLatestWith may seem like the obvious choice in this scenario, but there is a much simpler solution available.

Upon closer examination of the issue at hand, it becomes clear that our main challenge lies in filtering a dataset based on a specific search term.

The following code snippet demonstrates how this can be achieved:

export class HomeComponent implements OnInit, OnDestroy {
  constructor(private api: ApiService){}

  countries: any[];
  displayCountries: any[];

  destroy$ = new Subject<void>()
  countryControl = new FormControl();

  ngOnInit(){
    this.api.getAllCountries().subscribe(response => {
      this.countries = response;
      this.displayCountries = response;
    });

    this.countryControl.valueChanges.pipe(takeUntil(this.destroy$),debounceTime(100)).subscribe((value: string) => this.updateDisplayCountries(value))
  }

  private updateDisplayCountries(searchTerm: string): void {
    this.displayCountries = this.countries.filter(country => this.isCountrySearched(country, searchTerm.toLowerCase()))
  }

  private isCountrySearched(country: { name: { common: string, official: string} }, searchTerm: string): boolean {
    return country.name.common.toLowerCase().includes(searchTerm) || country.name.official.toLowerCase().includes(searchTerm)
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

Introducing three new variables:

  • displayCountries: represents the list of countries to be shown to users when they apply filters. It is a subset of the countries array, ensuring access to the complete list without complications associated with combineLatestWith.
  • countryControl: a FormControl aiding in utilizing input field values.
  • destroy$: manages prevention of memory leaks.

Key methods include:

  • ngOnInit: initiates the request for all countries and assigns retrieved data to both countries and displayCountries. Utilizes form control subscription for input value changes with precautions against memory leaks.
  • updateDisplayCountries: applies filtering logic to each country, converting search terms to lowercase to avoid case sensitivity issues.
  • isCountrySearched: compares search term against common or official country names, using lowercase conversion for consistency.
  • ngOnDestroy: safeguards against memory leaks.

A minor adjustment is required in the HTML of HomeComponent:

<input placeholder="Filter Countries" [formControl]="countryControl" type="text" value="">
<app-country-card *ngFor="let country of displayCountries" [country]="country"></app-country-card>

Addition of ReactiveFormsModule to module imports (e.g., AppModule) is essential for this functionality.

Answer №3

According to @churill's input, the combineLatest function is not deprecated but now requires parameters to be wrapped in a tuple. The previous version that accepted ...args as arguments is no longer supported. (Referring to

combineLatest(first$, second$, ..., nth$)
)

To correctly use combineLatest, follow these steps:

this.countries$ = combineLatest([
  this.loadCountries$, 
  this.searchTerm$
])
.pipe(
    map(([countries, searchTerm]) => yourFilterFunction(countries, searchTerm)),
);

Update:
Upon reviewing the rxjs operator sources ('

node_modules/rxjs/src/internal/observable/combineLatest.ts
')

export function combineLatest<T extends Record<string, ObservableInput<any>>>(
  sourcesObject: T
): Observable<{ [K in keyof T]: ObservedValueOf<T[K]> }>;

The method mentioned above allows for the following usage:

this.countries$ = combineLatest({
  countries: this.loadCountries$, 
  searchTerm: this.searchTerm$
})
.pipe(
    map(({countries, searchTerm}) => yourFilterFunction(countries, searchTerm)),
);

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

Why does Angular-CLI remove an old module when installing a new module using npm?

After adding the ng-sidebar module to my app, I decided to install a new module called ng2-d&d: npm install ng2-dnd --save However, after installing the new module, the old ng-sidebar module was removed from the app-module and an error occurred: C ...

Tips on creating a subsequent post for a file that has been uploaded to a Node Express server and then forwarded to a different API

We are currently working with an Express server in Node, where we upload a file on one route and then need to send this file to another endpoint for processing. However, the file is not stored locally on the server; it exists as an uploaded object. I&apos ...

I am looking to display data in Angular based on their corresponding ids

I am facing a scenario where I have two APIs with data containing similar ids but different values. The structure of the data is as follows: nights = { yearNo: 2014, monthNo: 7, countryId: 6162, countryNameGe: "რუსეთის ...

Tips for removing the default hover and click effects from a Material-UI CardActionArea

Check out the card sample with a lizard photo on https://material-ui.com/components/cards. When you hover over the cardActionArea, it will get darker or lighter based on the theme. Clicking on the card will make the picture change its brightness relative ...

Encountering issues when passing a string as query parameters

How can I successfully pass a string value along with navigation from one component to another using query parameters? Component1: stringData = "Hello"; this.router.navigate(['component2'], { queryParams: stringData }); Component2: ...

Transforming the jQuery tooltip to be shown in a column layout

Hello, I am currently using the displayTag library to showcase some tables. My goal is to include a tooltip on each display:column element by utilizing jQuery. Below is the code snippet in question: <c:set var="titleName"><wp:i18n key="FILENAME" ...

Using Node.js to establish communication between HTML and Express for exchanging data

I am faced with a challenge involving two pages, admin.hbs and gallery.hbs. The goal is to display the gallery page upon clicking a button on the admin page. The strategy involves extracting the ID of the div containing the button on the admin page using J ...

I am looking to modify the file name in the save as dialog box whenever the user performs a right-click

In my current project, I am utilizing PHP along with javascript/jQuery. I have a specific requirement where I need to alter the filename displayed in the save as dialog when a user right-clicks on an image and chooses to save it. For instance, I wish to ...

Angular 4/5 | Custom Dropdown Component

I have been working on a custom dropdown directive in Angular that I can attach to any DOM element. Below is the code for my directive: import { Directive, HostListener } from '@angular/core'; @Directive({ selector: '[appDropdown]' ...

What is the best way to retrieve the column that comes after a specific column?

I have a specific column and I want to extract the subsequent column in the dataset. nhood,column,variablecolumn Treasure Island,3,5 McLaren Park,1,2 Tenderloin,28,112 Lakeshore,14,8 Chinatown,15,103 Although I know the name of the second column, the na ...

Manage Blob data using Ajax request in spring MVC

My current project involves working with Blob data in spring MVC using jquery Ajax calls. Specifically, I am developing a banking application where I need to send an ajax request to retrieve all client details. However, the issue lies in dealing with the ...

Is there a way to simultaneously filter the mat table data source and its related asynchronous properties?

Scenario: My current setup consists of angular version 9.1.7, angular-material version 9.2.3, macOS version 10.14.6, and ngrx libraries (store, data, entity) version 9.1.2 Description: I currently have a functional material table with a MatTableDataSourc ...

Issue with Font Requests in Browsers when Using Angular 10 and SCSS with @font-face

I have a project built with Angular 10 that utilizes SCSS for styling. In my _typography.scss file, I have defined some @font-face rules pointing to the font files located in the assets/fonts directory. However, when I run the application, the browser does ...

Why is the return type for the always true conditional not passing the type check in this scenario?

Upon examination, type B = { foo: string; bar: number; }; function get<F extends B, K extends keyof B>(f: F, k: K): F[K] { return f[k]; } It seems like a similar concept is expressed in a different way in the following code snippet: functi ...

Guide to displaying the value of a field in an object upon clicking the inline edit button using TypeScript

Is it possible to console log a specific attribute of an object when clicking on the edit button using the code below? Please provide guidance on how to utilize the index to access the value of "name". Refer to the last line in the code with the comment. ...

What is the most effective way to use a withLatestFrom within an effect when integrating a selector with props (MemoizedSelectorWithProps) sourced from the action?

I am struggling to utilize a selector with props (of type MemoizedSelectorWithProps) in an effect inside WithLatestFrom. The issue arises because the parameter for the selector (the props) is derived from the action payload, making it difficult for withLat ...

PHP - Retrieve fully rendered HTML content from an external page

I am a beginner when it comes to AJAX and PHP, and I have been experimenting with using a PHP HTML DOM Parser to retrieve the HTML from an external website. However, I have encountered an issue where the fetched HTML is only the content that was initially ...

Tips for implementing lazy loading of modals in Angular 7's module structure

In a previous project, our team utilized a single "app module" that imported all necessary components, pipes, directives, and pages at the beginning of the application. However, this structure was not ideal as the app became slower with its growth. Upon t ...

Chrome Driver Protractor Angular 2 encountering issue with unclickable element

My issue is with clicking the second level menu options to expand to the third level. I have tried using browser.driver.manage().window().setSize(1280, 1024) in the before all section. Here is my code snippet: it('Should trigger the expansion of the ...