Implementing a Typeahead search in Angular using switchMap

The code for this Angular Typeahead is on my GitHub:
JeffreyBane/ALF-My-TypeAhead-App (github.com)

I know what you’re thinking. “I want to have a sexy looking web form with a sweet Angular Typeahead component so all the women and or men will want me.” So, spoiler – nobody is going to be more attracted to you because you’ve implemented a Typeahead search. BUT it will give your users a much more satisfying usability experience in your app.

That’s kind of the same! (That’s not the same at all.)

So, let’s continue our never-ending quest to understand all the higher order mapping operators by coding a Typeahead search. We’ve used concatMap on multiple occasions, in Avoiding Nested Subscriptions in Angular and also Populating Multiple Dropdowns in Angular Using forkJoin. Today we’ll be letting switchMap show its stuff and why it’s so useful in a situation like this – where the user will be changing their search term frequently, essentially every time they are done typing.

So, let’s jump right in. First thing we’ll need is Angular Material. We’ll be using the material autocomplete form control for our Typeahead. It takes care a lot of the details for us so it’s ideal for this example. Plus it looks sexy. (Again, the woman and or men are NOT going to want you for implementing a Typeahead in your code – I can’t stress this enough.)

We install Angular Material like so:

>> ng add @angular/material

I go into Angular Material in a bit more detail in this article Displaying a Loading Dialog on an Angular Parent Component While Child Components Load, but for now, just select yes to everything and pick a color theme. I’m going to get nutty and select Deep Purple/Amber for our example, but you pick whatever you want.

Let’s modify our app module to import the Angular Material items we need. Let’s also add Forms and Reactive Forms as we’ll use those later as well. Add these imports and also add them to the imports array:

App Module (app.module.ts)

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatInputModule } from '@angular/material/input';
import { MatAutocompleteModule } from '@angular/material/autocomplete';

.....

  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    FormsModule,
    ReactiveFormsModule,
    MatFormFieldModule,
    MatInputModule,
    MatAutocompleteModule
  ],

Now let’s start on our app. We’re going to create a system whereby new superheroes in the Marvel universe can see what superhero names are still available. (There’s not many after 15 years.) Once they select an available name from our Typeahead, they’ll be directed to a detail page where they can see more information about the name, how much it costs, their origin story, what superpowers they will gain, etc.

I’m fairly certain this is not how new superheroes come about. But let’s get going!

To start, we’ll create a service to return the available superhero names for our users to choose from. In the real world, this would obviously be a database operation where our API would submit a query to our backend to retrieve the list of names and eventually detail information. However, in this article we’re just going to focus on the Typeahead, so in our service we’ll simply return a JSON object array. We’ll also filter that array on items that contain any of the characters in the search term the user has entered.

Let’s create that very service:

>> ng g s type-ahead

And the contents:

Typeahead service (type-ahead.service.ts)

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class TypeAheadService {

  getDropDownData(searchValue: string): Observable<any[]> {

    const mockJson = [

      { id: '1', value: 'The Visible Girl', disabled: false },
      { id: '2', value: 'Anxiety Man', disabled: false },
      { id: '3', value: 'Acne Boy', disabled: false },
      { id: '4', value: 'The Human Goiter', disabled: false },
      { id: '5', value: 'Vegan Zombie', disabled: false },
      { id: '6', value: 'Windows Update', disabled: false },
      { id: '7', value: 'JavaScript Diaper', disabled: false },
      { id: '8', value: 'The Incredible Middle Manager', disabled: false },
      { id: '9', value: 'Commander Reflux', disabled: false },
      { id: '10', value: 'Urinal Mint', disabled: false },
      { id: '11', value: 'Pause Squat', disabled: false },
      { id: '12', value: 'The Turd', disabled: false },
      { id: '13', value: 'Extended Warranty', disabled: false }


    ];

    return of(mockJson.filter(item =>
      item.value.toLowerCase().indexOf(searchValue.toLowerCase()) > -1));

  }

}

So again, this would be a database call in a real app, and the filtering would occur there, but for our example we’re just going to return the list of available names and filter on the variable “searchValue” passed to our service method getDropDownData. Note each item has a value of “disabled: false” added to it. You’ll see why in a bit.

Our app also is going to have a search form and a detail form as the landing page once a search item is selected in our Typeahead. We’ll use Angular Routing and set up our app component form with pretty much nothing but some text and a router outlet:

App component (app.component.html)

<h1>The Big List of MCU Unused Superhero Names.</h1>
<h2>Please Select Your New Identity!</h2>
<router-outlet></router-outlet>

Now let’s create our 2 components, the search and detail:

>> ng g c type-ahead  

>> ng g c detail-page

And then one last item before the fun begins, let’s add our routing with a default route of our Typeahead search component:

App routing module (app-routing.module.ts)

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DetailPageComponent } from './detail-page/detail-page.component';
import { TypeAheadComponent } from './type-ahead/type-ahead.component';

const routes: Routes = [

  {
    path: 'TypeAhead',
    component: TypeAheadComponent
  },
  {
    path: 'DetailPage',
    component: DetailPageComponent
  },
  { path: '',   
    redirectTo: '/TypeAhead', 
    pathMatch: 'full' }, 
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Fabulous, let’s get Typeaheading! (That’s not a word.) We’ll go ahead and enter the guts of our Typeahead form, then we’ll talk about it bit by bit:

Typeahead form (type-ahead.component.html)

<div>
    <form>
        <mat-form-field style="margin-left: 20px; width: 300px;">
            <mat-placeholder>Type some characters, future hero</mat-placeholder>
            <input matInput [formControl]="searchBox" [matAutocomplete]="ac" id="searchBox" />

            <mat-autocomplete #ac="matAutocomplete" (optionSelected)="itemSelected($event)">
                <mat-option *ngFor="let option of searchResults; " [disabled]="option.disabled" [value]="option.id">
                    {{ option.value }}
                </mat-option>
            </mat-autocomplete>
        </mat-form-field>
    </form>
</div>

So let’s look at the 2 main parts of our form. First we’re wiring up a mat-form-field wrapper for everything. The first item within is a placeholder, which just looks impressive, and also gives some rudimentary instruction, in this case “Type some characters, future hero”

        <mat-form-field style="margin-left: 20px; width: 300px;">
            <mat-placeholder>Type some characters, future hero</mat-placeholder>

Next the actual matInput for our textbox to type in:

<input matInput [formControl]="searchBox" [matAutocomplete]="ac" id="searchBox" />

We also wire our input up to a formControl in the code behind called “searchBox”, so we can access it programmatically. Finally we wire up this input to our auto complete element which we’ll discuss next. This is done by setting the [matAutoComplete] property of the matInput to a template reference of “ac” (though you could use any name):

[matAutocomplete]="ac"

This is the template reference we’ll specify on our autocomplete so the two can interact. And that brings us to our Angular Material auto complete item:

            <mat-autocomplete #ac="matAutocomplete" (optionSelected)="itemSelected($event)">
                <mat-option *ngFor="let option of searchResults; " [disabled]="option.disabled" [value]="option.id">
                    {{ option.value }}
                </mat-option>
            </mat-autocomplete>

As stated, we’ll assign the autocomplete a template reference of “#ac” so our input can talk to it. We’ve also got a method tied to the optionSelected event of the auto complete. This fires, (you guessed it future hero!) when one of the autocomplete items is clicked on. That’s where we’ll determine what item was selected and route to our detail form. The remainder is a pretty typical ngFor, which will loop through each item of our searchResults array and bind them to the autocomplete. However, do note we’re also setting the disabled attribute of each item to the disabled property of each object in our JSON array. Again, you’ll see why this is when we get into the code behind, but every item from our service has “disable = false”, so they are all selectable.

Let’s see what the fruit of our thus far labor looks like. In order to get this code to compile, temporarily add these 3 items to the component class in the typescript file.

Typeahead component (type-ahead.component.ts)

export class TypeAheadComponent implements OnInit {

  public searchBox = new FormControl();
  public searchResults: any[] = [];

  itemSelected(event): void {
  }

We’ll delete all that in a minute, but now our code will run, and you can see what our form looks like in all its glory, albeit with no items being returned when we type:

Angular Typeahead example

You can see the placeholder already knows how to behave and gives a very nice effect when we click in the input and type something. Go ahead and delete the items we temporarily put into the component, and we’ll start building the real component.ts file a piece at a time. Let’s start with all the class variables we’ll need:

Typeahead component (type-ahead.component.ts)

export class TypeAheadComponent implements OnInit {

  public searchBox = new FormControl();
  public clearedBox = true;
  public searchTerm = '';

  // Object bound to autocomplete
  public searchResults: any[] = [];

  // Empty default object for when the search box doesn't have anything in it
  public defaultResults: any[] = [];


  // JSON to display when no items are returned from the service
  public noResult: any = {
    id: 0,
    value: 'No names match criteria',
    disabled: true

  };
  public noResults: any[] = [];

We start by declaring our FormControl called searchBox which is the input on our form. Then we create a Boolean called clearedBox (more on that in a bit) and a string to hold our searchTerm called, um, searchTerm (duh.) Next searchResults is the array that is bound to our autocomplete and will be assigned the values returned from our service. We then set up 2 more arrays:

  • defaultResults which is an empty array we use to initilze the list to nothing.
  • noResults which is what we want to display if the user types something that has NO match in our list. (Note disabled is set to true for this item. Again you’ll see why in a minute.)

We’ll pass 2 services into the constructor, the router service and of course our Typeahead service:

  constructor(
    private _TypeAheadService: TypeAheadService,
    private _router: Router
  ) { }

Now onto the ngOnInit method and all the nougaty goodness that makes this form work. Here’s the entire ngOnInit – we’ll break it down bit by bit after:

  ngOnInit(): void {

    // Add our not found item to the noResults array
    this.noResults[0] = this.noResult;
    // set the inital value of searchResults to our empty array
    this.searchResults = this.defaultResults;
    // Setup a debounce time constant of 1/2 a second.
    const DEBOUNCE_TIME = 500;


    // get values from the service
    this.searchBox.valueChanges.pipe(
      debounceTime(DEBOUNCE_TIME),
      switchMap(changedValue => {
        if (changedValue.trim() !== '') {
          this.searchTerm = changedValue.trim();
          this.clearedBox = false;
          return this._TypeAheadService.getDropDownData(this.searchTerm);
        } else {
          this.searchTerm = '';
          this.clearedBox = true;
          return of(this.defaultResults);
        }
      }
      )
    ).subscribe(data => {
      if (data.length === 0) {
        if (this.clearedBox) {
          this.searchResults = data;
        } else {
          this.searchResults = this.noResults;
        }
      } else {
        this.searchResults = data;
      }
    }
    );
  }

Before we call our service, we do some simple things like adding our noResult object to the noResult array, set searchResults to defaultResults (blank) and also setup a constant for debounce time. (I put the constant definition right above the service call for readability, you’d put it in the class variables normally).

And now the actual subscription. We’re going to subscribe to an event called valueChanges of our searchBox control. In Angular, FormControls (and some other form objects) have a really useful event exposed called valueChanges. valueChanges is an observable that we can subscribe to so that each time the value of the textbox changes we can do stuff, in this case, filter our autocomplete list of items.

Starting with valueChanges, we pipe the results:

this.searchBox.valueChanges.pipe(

We pipe the results to the debounceTime operator. This guy is a lifesaver. It allows you to wait a certain amount of time before emitting. In our case we set this to a half a second, using our constant:

debounceTime(DEBOUNCE_TIME),

Now if the user fires valueChanges again (i.e., types something else within half a second), the timer is reset and the half a second count begins again. Why do we do this? Well imagine if we didn’t do this – Our autocomplete would fire off populating a new list after every character entered, as the user types. If there were a delay in the results being returned as would be typical in a real-world app, we’d be firing off API calls left and right before any of them had a chance to return results, putting more stress on the backend / db than needs be. By using debounce, we create a magical, pleasant experience where the webform essentially says to the user – “Ok, are you done typing? Since it’s been half a second, I think you are, and I’ll go populate the autocomplete list now. Also have a great day!”

Now we need to emit this value (what the user has typed) to a new observable. We do this using a higher order mapping operator. In our case switchMap is going to work great with our Typeahead. switchMap unsubscribes from any prior inner Observable and subscribes to any new inner Observable. This has the very desirable effect of stopping any prior inner Observable before processing the new one, exactly what we want in this case. In other words, if switchMap could speak English it would say something like – “Hey, we got a new value emitted here, whatever you were doing – just throw it out the window and process this new value. Also, have a great day!” (Our sentient code creatures are apparently very polite.)

switchMap(changedValue => {

We’ve called our debounced value “changedValue” and we’ll just simply check that the change wasn’t the user clearing the textbox out. Also, If the user cleared out the text or typed something that had no matches, we’ll handle all that shortly.

if (changedValue.trim() !== '') {

Passing that check, we know the user actually did type something, so let’s do a few things. First, we’ll trim the value:

this.searchTerm = changedValue.trim();

Next we’ll set the variable we have called “clearedBox” to false. We use this Boolean to indicate if the user cleared out the text box or not and handle that in the subscribe. Since we checked for a value above in our if, we know this is false and the user hasn’t cleared the textbox:

this.clearedBox = false;

Finally we pass the search term to the service and return the Observable to our subscribe:

return this._TypeAheadService.getDropDownData(this.searchTerm);

Otherwise (else), the user did indeed clear the box. In that case, we set searchTerm to “”, set clearedBox to true, and return an Observable of defaultResults which again we simply defined earlier as an empty array:

} else {
  this.searchTerm = '';
  this.clearedBox = true;
  return of(this.defaultResults);
}

Finally, the subscribe:

).subscribe(data => {

So, we’ve called our service and MAYBE gotten some data back, we don’t know yet – the user could have typed complete gibberish into the search box, or some characters that don’t match any name in our list. So let’s check if the service returned any values:

if (data.length === 0) {

Now you’ll see what “clearedBox” is used for. Recall a bit back, prior to our subscribe, we’re returning one of 2 observables. Either the result set from our service (if the user actually typed something) or our default result (if the user cleared out the textbox). If the latter, we also set clearedBox to true. Thanks to that good work, when we subscribe, we will now automatically get the appropriate observable plus we’ll know if the user cleared the textbox. So all we really need to check for now is did the service return any data, i.e., is the length 0 or not. The reason being- we want to differentiate if the service returned no rows (the user typed something that didn’t return any names) or the user cleared the textbox. If we set clearedBox to true earlier, the user indeed cleared the checkbox, and we simply display the defaultResults we are returning automatically in the observable. If clearedBox is false and the service data.length = 0, we know the service didn’t return any matching rows. In that case, we want to display the noResults array (the message saying, “No names matched”.) This way we’re not telling them the service returned no results if they cleared the textbox, which would be annoying. So if they cleared the textbox, display the observable, otherwise display the noResults message:

if (this.clearedBox) {
  this.searchResults = data;
} else {
  this.searchResults = this.noResults;
}

Else (if we have a data.length that doesn’t equal 0) the user typed a search term that had some matches, the service indeed returned some data and that’s what we’ll display in the Material autocomplete:

 } else {
  this.searchResults = data;
 }
}

Now the data our autocomplete display is all taken care of, for the last bit of our component, we’ll handle the itemSelected event and route to our detail page, passing along the id of the selected item, utilizing the state navigation option.

itemSelected(event): void {
    this._router.navigate(['/DetailPage'], { state: { selectedId: event.option.value } });
}

Ok. Let’s have a look at all this working! But first let’s code up our simplified detail component as that’s the final piece we’ll need. The component code:

Detail component (detail-page.component.ts)

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-detail-page',
  templateUrl: './detail-page.component.html',
  styleUrls: ['./detail-page.component.scss']
})
export class DetailPageComponent implements OnInit {
  selectedId: number;

  constructor(private _router: Router) {

    this.selectedId = this._router.getCurrentNavigation().extras.state.selectedId;
   }

  ngOnInit(): void {
  }
}

In our itemSelected() event handler in the Typeahead form, we passed along the option value that was selected using a variable we called selectedId.
Our detail page does nothing more than grab this variable from the navigation state and assign it to a local variable of the same name, selectedId. In a real-world app, you’d take this primary key, and run a query to return detail information of the item they selected. In our unused superhero name app, we’d requery the database to get all the specifics on the name they selected, such as what superpowers it comes along with, their origin story, pricing, etc. and ultimately a way to purchase the name. But again, that’s beyond the scope of the Typeahead that’s being demonstrated in this article, so our detail form will simply display the key of the item selected:

Detail form (detail-page.component.html)

<h3>You selected the item with id of {{ selectedId }} !!</h3>

And with that, our unused MCU superhero name generator lookup thingy is ready to roll! Let’s fire off the app and see how everything functions. Start the app and type the word “the” into our input:

Angular Typeahead example

Excellent – you can see the values are filtered on anything that contains the word “the”. Notice how the debounce of 1/2 second is also working. If you type “the” one letter at a time very slowly, you can see the list is refreshed for every letter you type. But if you type “the” somewhat quicker, the debounce ensures that the list is only retrieved when you are done typing. (haven’t touched a key for the 1/2 second, firing the valueChanges() event.) Debounce makes for a pleasant UX experience and prevents our API from getting called more than necessary. Win / win for all.

Back in our app, go ahead and select “The Incredible Middle Manager” (No doubt his superpowers would include scheduling meetings and micromanaging in a single bound):

And voila, we have the primary key (id) of the item selected in the previous form by our Typeahead! Let’s do a few more things to see some of the remaining functionality we coded. First off, return to the type ahead form and type some gibberish, “xyz” or anything that won’t return any results:

Angular Typeahead example

Remember how we set disabled: true on our no results item? Now you can see why – the user gets a message that states nothing was found for that search string, but it’s not something they can select. There’s a couple of ways to handle a no results message, but I like this way for a few reasons. First, it’s not a lot of code and second, the message shows up in the autocomplete, exactly where the user expects their results to show up. If we put “No names found” in a separate label somewhere else on the screen, it would be a bit jarring, and I like consistency. So with the simple addition of “disabled” true or false to each item, we can display whatever we want in the results AND prevent the user from clicking on informational messages. But feel free to handle this message differently if you care to. Finally, go ahead and clear out what you typed in the box:

Angular Typeahead example

Notice how we don’t get the “No names found” message any longer. We wouldn’t want to tell the user nothing was found every time they deleted the text from the input. Our user might start screaming uncontrollably “Duh, I haven’t typed anything yet!” and perhaps throwing office furniture, which would suck.

Note: If you grab this code from my GitHub, I’ve included comments in the Typeahead component as to where you would open and close a loading dialog so the user would get a “Please wait…” type message while the data is retrieved:

      switchMap(changedValue => {
      // Close Loading Panel
        if (changedValue.trim() !== '') {
          this.searchTerm = changedValue.trim();
          // Open Loading Panel

We had no need for these dialogs with our no latency app and they are outside the scope of the article but have a read here Displaying a Loading Dialog on an Angular Parent Component While Child Components Load for all the loading dialog goodness you can handle.

For your reading pleasure:

Higher Order Observables (switchMap at the bottom)
https://blogs.msmvps.com/deborahk/higher-order-observable/

Navigation Behavior Options (state) https://angular.io/api/router/NavigationBehaviorOptions#state

Our good friend debounce
https://rxjs.dev/api/operators/debounce

Populating Multiple Dropdowns in Angular Using forkJoin

The code for this article is available on my GitHub:
JeffreyBane/ALF-My-ForkJoin-App (github.com)

We looked at using concatMap to populate a dropdown prior to assigning a selected index in this article: Avoiding Nested Subscriptions in Angular. Waiting for an observable to complete before starting another observable has many use cases. However today let’s look at a case where we’re going to fire off multiple API calls and we don’t care about the order in which they are run. In fact, we want them to execute in parallel for the best performance.

The use case being – “Go fire off these 3 observables, I don’t care about order, but once they are ALL done, I want to take some other action.”

As always, let’s start a fictitious company. We run a car dealership (mainly because I can’t think of anything else right now). We sell high quality new and used cars for no more than MSRP. In fact, we sell our cars at a discount, treat our customers well, and never lie to them.

Pretend is fun.

For our inventory lookup tool, we use an Angular app that salespeople can use to find out details about the cars on our lot. In our simplified example, our app has dropdowns for things like color, year, and transmission. Our car detail record stores the selected value for each dropdown in the form of the key from a hypothetical dropdown table that stores all our dropdown key and value pairs.

So, to sum up, whenever a car is selected, we have:

  • 3 dropdowns we want to populate
  • We don’t care what order they are populated in
  • However once ALL of the dropdowns populate, we want to take the action of selecting the correct values for each vehicle
  • As well as populating some other form fields

Let’s get started with a service:

>> ng g s vehicle-information

Inside this service, we’re going to simulate a table that stores all our dropdown key / values, classified by type. getDropDownData() will return the values for each select, filtered on the type passed in. We also have another method that returns a vehicle detail record filtered on the vehicleId passed in, getVehicle().

Vehicle Information Service (vehicle-information.service.ts)

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class VehicleInformationService {

  getDropDownData(dropDownType: string): Observable<any[]> {

    const mockJson = [

      { id: 1, type: 'Color', value: 'White' },
      { id: 2, type: 'Color', value: 'Black' },
      { id: 3, type: 'Color', value: 'Silver' },
      { id: 4, type: 'Color', value: 'Blue' },
      { id: 5, type: 'Color', value: 'Red' },
      { id: 6, type: 'Color', value: 'Other' },

      { id: 7, type: 'Year', value: '2022' },
      { id: 8, type: 'Year', value: '2021' },
      { id: 9, type: 'Year', value: '2020' },
      { id: 10, type: 'Year', value: '2019' },
      { id: 11, type: 'Year', value: '2018' },

      { id: 12, type: 'Transmission', value: '5 Speed Manual' },
      { id: 13, type: 'Transmission', value: '6 Speed Manual' },
      { id: 14, type: 'Transmission', value: 'Automatic' }

    ];
    return of(mockJson.filter(item => item.type === dropDownType));
  }

  getVehicle(vehicleId: number): Observable<any> {

    const mockJson = [

      { id: 1, make: 'Ford', model: 'Luxoboat', price: 29999, color: 1, year: 9, transmission: 14 },
      { id: 2, make: 'Bentley', model: 'Excess', price: 249999, color: 4, year: 7, transmission: 14 },
      { id: 3, make: 'BMW', model: 'Service Loaner', price: 69999, color: 2, year: 7, transmission: 13 }

    ];
    return of(mockJson.filter(item => item.id === vehicleId)[0]);
  }
}

Now on to our form. For now, let’s start with doing nothing but populating the 3 dropdowns to demonstrate how forkJoin works. We’ll use some css classes to simulate a “please wait” type message. For now, I’ll just use a ‘light’ them and ‘normal’ theme – ‘light’ means data is loading, with ‘normal’ meaning the data load is complete. Creating a “Please Wait / Data Loading” type of dialog isn’t the purpose of this article but if you want to have a look at a full and complete example of creating a “Data loading” type dialog, have a look at Displaying a Loading Dialog on an Angular Parent Component While Child Components Load.

So back to our form:

App Component Form (app.component.html)

<div [ngClass]="divClass">

  <h2>Welcome to Big Honest Jeff's Big Car Lot of Big Joy</h2>
  <h3>Inventory system</h3>
  
      <div>
          Color: <select formControlName="colorSelect">
              <option *ngFor="let c of colors" [value]="c.id">
                  {{c.value}}
              </option>
          </select>
      </div>
      <div>
          Year: <select formControlName="yearSelect">
              <option *ngFor="let y of years" [value]="y.id">
                  {{y.value}}
              </option>
          </select>
      </div>
      <div>
          Transmission: <select formControlName="transmissionSelect">
              <option *ngFor="let t of transmissions" [value]="t.id">
                  {{t.value}}
              </option>
          </select>
      </div>
  
  </div>

And in the scss file, our 2 simple classes:

App Component Style Sheet (app.component.scss)

.light {
    opacity: .2;

}
.normal {
    opacity: 1;
}

And then finally our component to populate the drop downs. setTimeout() will simulate loading behavior by delaying our form population by 2 seconds:

App Component (app.component.ts)

import { Component } from '@angular/core';
import { forkJoin } from 'rxjs';
import { VehicleInformationService } from './vehicle-information.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  colors: any[];
  years: any[];
  transmissions: any[];
  vehicle: any;
  divClass = 'light';


  constructor(private _vehicleInformationService: VehicleInformationService) {

    this.divClass = 'light';
    forkJoin([
      _vehicleInformationService.getDropDownData('Color'),
      _vehicleInformationService.getDropDownData('Year'),
      _vehicleInformationService.getDropDownData('Transmission')
    ]).subscribe((dds) =>

      setTimeout(() => {

        this.colors = dds[0];
        this.years = dds[1];
        this.transmissions = dds[2];

        this.divClass = 'normal';

      }, 2000)

    );
  }
}

So when we first fire up the app, you’ll notice that the screen has the ‘light’ css class applied while we wait for the 3 dropdowns to be populated:

Once everything is bound to our ngFor variables for our dropdowns, we set the css class to ‘normal’, restoring a complete look:

Fabulous!

Before we add the remainder of our code, let’s have a look at what forkJoin is doing and how it works. Unlike your traditional call to a service method that returns an observable, forkJoin actually takes an array of method calls. This can be 2, 6, or even 43 or more:

    forkJoin([
      _dropDownService.getDropDownData('Color'),
      _dropDownService.getDropDownData('Year'),
      _dropDownService.getDropDownData('Transmission')
    ])

forkJoin then returns, you guessed it, an array of values. Assigning them to variables is as simple as using the array index that corresponds to the order in which they were called:

    ).subscribe((dds) => 
        this.colors = dds[0];
        this.years = dds[1];
        this.transmissions = dds[2];
    )

This is a super useful way to fire off multiple requests when we don’t care about what order they complete in. However, our original use case said we want to do something AFTER these 3 calls complete. In this case, we want to select all the appropriate selected index values for the dropdowns, as well as populate some other form fields.

So let’s do that very thing. We’ll need a higher order operator like concatMap or switchMap to chain our observables together. Since our observables are all just simple simulated API calls (what would typically be an HTTP get request) that return just one value, it’s not super important which operator we use. If any of our observables were to emit more than one value, it would become quite important which operator we use (article forthcoming….) But in this example, we’ll just use concatMap. Because I like concatMap. (To see switchMap really shine check out Implementing a Typeahead search in Angular using switchMap when you have the urge.)

We’ll start in this case by calling our getVehicle() method, storing the retuned value in a variable called ‘vehicle’ and then chaining that to our forkJoin. We’ll hard code an id for this example, but in the real world you’d have a list of vehicles and clicking on a vehicle would bring you to this very detail page and call getVehicle() with the appropriate id. In this case we’ll just use “1”. Once all the observable values are returned in the subscribe, we can assign our selected indexes as well as populate a form field or two. Let’s look at the revised HTML first:

App Component HTML (app.component.html)

<form [formGroup]="myFormGroup">
    <div [ngClass]="divClass">

        <h2>Welcome to Big Honest Jeff's Big Car Lot of Big Joy</h2>
        <h3>Inventory system</h3>

        <div>
            Make: <input type='textbox' formControlName="make">
            Model: <input type='textbox' formControlName="model">
            Price: <input type='textbox' formControlName="price">
        </div>


        <div>
            Color: <select formControlName="colorSelect">
                <option *ngFor="let c of colors" [value]="c.id">
                    {{c.value}}
                </option>
            </select>
        </div>
        <div>
            Year: <select formControlName="yearSelect">
                <option *ngFor="let y of years" [value]="y.id">
                    {{y.value}}
                </option>
            </select>
        </div>
        <div>
            Transmission: <select formControlName="transmissionSelect">
                <option *ngFor="let t of transmissions" [value]="t.id">
                    {{t.value}}
                </option>
            </select>
        </div>


    </div>
</form>

Since we’re using a big boy form now (albeit an absolutely hideous, unstyled form), we need to add an import to our app.module so add ReactiveFormsModule to the imports array:

App Module (app.module.ts)

  imports: [
    BrowserModule,
    AppRoutingModule,
    ReactiveFormsModule
  ],

And finally let’s look at the code of our completed component:

App Component (app.component.ts)

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { concatMap, delay, forkJoin } from 'rxjs';
import { VehicleInformationService } from './vehicle-information.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {

  colors: any[];
  years: any[];
  transmissions: any[];
  vehicle: any;
  divClass = 'light';

  colorSelect: FormControl;
  yearSelect: FormControl;
  transmissionSelect: FormControl;
  make: FormControl;
  model: FormControl;
  price: FormControl;

  myFormGroup: FormGroup;


  ngOnInit(): void {

    this.colorSelect = new FormControl('');
    this.yearSelect = new FormControl('');
    this.transmissionSelect = new FormControl('');
    this.make = new FormControl('');
    this.model = new FormControl('');
    this.price = new FormControl('');

    this.myFormGroup = new FormGroup({
      colorSelect: this.colorSelect,
      yearSelect: this.yearSelect,
      transmissionSelect: this.transmissionSelect,
      make: this.make,
      model: this.model,
      price: this.price
    })
  }


  constructor(private _vehicleInformationService: VehicleInformationService) {

    this.divClass = 'light';

    _vehicleInformationService.getVehicle(1).pipe(concatMap((data) => {
      this.vehicle = data;
      return forkJoin([
        _vehicleInformationService.getDropDownData('Color'),
        _vehicleInformationService.getDropDownData('Year'),
        _vehicleInformationService.getDropDownData('Transmission')
      ]).pipe(delay(2000))
    }
    )).subscribe((dds) => {
      console.log(this.vehicle);
      this.colors = dds[0];
      this.years = dds[1];
      this.transmissions = dds[2];

      // set form values
      this.colorSelect.setValue(this.vehicle['color']);
      this.yearSelect.setValue(this.vehicle['year']);
      this.transmissionSelect.setValue(this.vehicle['transmission']);
      this.make.setValue(this.vehicle['make']);
      this.model.setValue(this.vehicle['model']);
      this.price.setValue(this.vehicle['price']);

      this.divClass = 'normal';
    });
  }
}

So we’re setting up a proper form in the OnInit then getVehicle(1) begins our chain. (use any id you’d like. As long as it’s 1, 2, or 3.) We then pipe that to concatMap and assign the value that getVehicle() returns to our ‘vehicle’ variable (I’m very creative with naming, you don’t have to tell me). Then instead of setTimout like we used in the previous example, we pipe that to the delay operator. If you’ve never piped to the delay operator, it’s a very useful thing for testing, making it easy to simulate long API or http calls. You can read more about delay in the links at the end of the article. Once we finally subscribe and get our values back from our forkJoin call, we populate our 3 dropdowns then populate everything else on our form using all the values stored in the vehicle object (vehicle 2 in this case):

You can see how forkJoin becomes a very useful tool for returning multiple values when you truly don’t care about what order the observables are returned in, which for many things like dropdown lists, is typically the case.

In summary, I hope you’re getting as excited as I am about all the options you have in rxjs / Angular for piping your results to other operators, piping yet again, and making all kinds of magic happen (I really need a girlfriend).

NOTE: One sort of gotcha when using forkJoin is that if any of the observables in the array generate an error, you’ll lose the values of ALL the observables. In this particular example, I wouldn’t have a problem with that – If some dropdown couldn’t be populated, we don’t want the user to try to edit a form that’s only half populated. I’d like to generate an actual error and prevent any editing of the form data until the situation is corrected.

However, if you DO indeed want, say 2 dropdowns populated if only 2 calls succeed, a way around this (there’s a good example of this method in the learnrxjs.io link at the end of the article) is to simply handle the error on each observable using catch error. So instead of this:

_vehicleInformationService.getDropDownData('Color')

Each of your observables in the forkJoin array would have its own error handler like this:

_vehicleInformationService.getDropDownData('Color').pipe(catchError(error => of(error))),

Now if any call fails, it will simply catch the error and return an error in that array element. As the final step to this method, we now need to test for an error in our assignment code in our subscribe method:


    )).subscribe((dds) => {
      
      if (!(dds[0] instanceof Error)) {
        this.colors = dds[0];
      }

      if (!(dds[1] instanceof Error)) {
        this.years = dds[1];
      }

      if (!(dds[2] instanceof Error)) {
        this.transmissions = dds[2];
      }

The following snippet in our component will demonstrate this:

App Component (app.component.ts)

    this.divClass = 'light';

    _vehicleInformationService.getVehicle(1).pipe(concatMap((data) => {
      this.vehicle = data;
      return forkJoin([
        throwError(() => new Error('Some error occurred'))
.pipe(catchError(error => of(error))),
        _vehicleInformationService.getDropDownData('Year')
.pipe(catchError(error => of(error))),
        _vehicleInformationService.getDropDownData('Transmission')
.pipe(catchError(error => of(error)))
      ]).pipe(delay(2000))
    }
    )).subscribe((dds) => {

      if (!(dds[0] instanceof Error)) {
        this.colors = dds[0];
      }

      if (!(dds[1] instanceof Error)) {
        this.years = dds[1];
      }

      if (!(dds[2] instanceof Error)) {
        this.transmissions = dds[2];
      }

      this.colorSelect.setValue(this.vehicle['color']);
      this.yearSelect.setValue(this.vehicle['year']);
      this.transmissionSelect.setValue(this.vehicle['transmission']);
      this.make.setValue(this.vehicle['make']);
      this.model.setValue(this.vehicle['model']);
      this.price.setValue(this.vehicle['price']);
      this.divClass = 'normal';
    });

Now each item in the forkJoin array is being piped to catchError, which will handle the error if one is raised on each individual call. Also, notice in the first item in the forkJoin array, we’ve replaced the call to get the Color values with a throwError() to generate an error. However, since we’re catching it as well, our subscribe will no longer populate that dropdown if an error is returned:

      if (!(dds[0] instanceof Error)) {
        this.colors = dds[0];
      }

You can see now the dropdowns are populated except for the one that threw an error:

Again, dropdowns are probably not the best use case for this error handling technique, but now you know how to handle preserving observable values from forkJoin should one or more of them happen to fail, should that suit your needs.

You read now:

All things forkJoin: https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin


Delay operator: https://www.learnrxjs.io/learn-rxjs/operators/utility/delay

Avoiding Nested Subscriptions in Angular

The code for this article is available on my GitHub:
https://github.com/JeffreyBane/ALF-My-Nested-Subscription-App

Nested subscriptions are a big no-no in Angular for many reasons. Nested subscriptions can lead to an unmanageable mess of multiple (even hundreds or thousands of) subscriptions that can cause memory leaks and destroy the performance of your site.

Even worse for the developer – code inspection tools like SonarQube won’t let you get away with nested subscriptions, potentially making your code undeployable.

Say with me – “Nested subscriptions are bad and I promise I’m going to read the links at the end of this article. HOWEVER, I really need to fix this right now as my current sprint is over in 6 minutes.” Ok, fair enough.

Let’s look at a common use case.

Say you have a form with a dropdown containing a list of items from a master table, in our case we’ll say it’s a list of departments. You’re also retrieving an entity from a database that contains a foreign key value to that list, in our example, an employee record is returned with a Department ID foreign key.
Once you have the employee record, you’ll display an Employee Edit form, displaying various employee related fields on the form as well as select the value in the dropdown list that displays the employee’s current department.
This dropdown also allows the department to be changed by selecting a different value in the dropdown:

The “problem” with this is you can’t just fire off a bunch of async observables and hope to set the Department ID value properly. You have to wait until the dropdown list is populated before you can set the selected id value, so we need a synchronous operation.

Your first thought might be, let me call an observable to retrive the list of department entities then populate a variable bound to the dropdown.
ONCE THAT LIST IS POPULATED, (<= main point here), I’ll grab the employee data and then change the selected index of the dropdown to the Department ID of the employee in question.

So plan A becomes:

  • Subscribe to the Departments observable.
  • In the complete function of the subscribe, bind the data to the Departments dropdown to populate it. Now call and subscribe to the observable that retrieves employee data.
  • Once that observable completes, set the correct Department ID value in the dropdown.

Let’s have a look at that code with the above nested subscription. First let’s look at the form and then the service we’ll be using:

Employee Form (employee.component.html)

<p>Employee Edit</p>
<form [formGroup]="employeeForm">

Name: <input type="text" value="{{ name }}"/><br>
Status: <input type="text" value="{{ status }}"/><br>

Department: <select formControlName="departmentsSelect">
    <option *ngFor="let d of departments" [value]="d.deptId">
        {{d.departmentName}}
    </option>
</select>
<br>

<button>Submit</button>
</form>

Employee Service (employee.service.ts)

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class EmployeeService {

  constructor() { }

  getEmployeeDetails(empId: number): Observable<any> {
   const mockJson = {name: 'John Doe', status: 'Active', deptId: 2 };
   return of(mockJson);
  }

  getDepartmentList(): Observable<any[]> {
    const mockJson = [
    {deptId: 1, departmentName: 'Human Resources'},
    {deptId: 2, departmentName: 'Development'},
    {deptId: 3, departmentName: 'Sales'}
    ];
    return of(mockJson);

  }
}

So two very simple mocked service calls, get the employee details and get the list of departments. Now here’s our component file that uses a nested subscription:

Employee Component (employee-nested.component.ts)

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Subscription } from 'rxjs';
import { EmployeeService } from './employee.service';

@Component({
  selector: 'employee',
  templateUrl: './employee.component.html',
})
export class EmployeeNestedComponent implements OnInit {

  departments: any[];
  name = '';
  status = '';
  deptId = 0;
  departmentsSelect: FormControl;
  employeeForm: FormGroup;
  sub1: Subscription;
  sub2: Subscription;



  constructor(private _employeeService: EmployeeService) { }

  ngOnInit(): void {


    this.departmentsSelect = new FormControl();

    this.employeeForm = new FormGroup({
      departmentsSelect: this.departmentsSelect
    }
    );


      this.sub1 = this._employeeService.getDepartmentList()
         .subscribe((deptData) => {
         this.departments = deptData;
         this.sub2 = this._employeeService.getEmployeeDetails(123) 
            .subscribe(data => {

            // Set selected dropdown value:
            this.employeeForm.controls.departmentsSelect.setValue(data.deptId); 
            this.status = data.status;
            this.name = data.name;

      });
    });

  }

}

In the above, our nested subscribe has the effect of waiting until the getDepartmentList() call is complete before firing off getEmployeeDetails(123), and ultimately setting the correct dropdown value in the nested subscribe.
Now technically this works. However, notice we have 2 subscriptions to manage, sub1 and sub2. You can also imagine how this would look if we had say 3, 4 or 10 nested subscriptions. But again, most importantly – this is not getting past static code analysis – Sonarqube and similar products will publicly mock you for using a nested subscription.

What’s the better way? Plan B – Enter the higher order mapping operators. In short, these operators transform an observable into another observable.
Each value from an “outer” observable is mapped to a new “inner” observable.
This inner observable is then automatically subscribed to (and unsubscribed from).

Let’s have a look at an example. In this case we’ll use concatMap, although it’s not the only option (switchMap would work well here also. See switchMap in action in this article: Implementing a Typeahead search in Angular using switchMap), but concatMap has some neat behavior you’ll want to know about for your code arsenal. In a nutshell, concatMap does not subscribe to the next observable until the previous completes – essentially it’s the way to run observables in order, synchronously.


You can imagine this having value in many use cases, not just our dropdown example. For instance, if you ever have some complex database operations where an insert MUST occur before an update, or a series on inserts must occur in order, etc. So, let’s have a look at our new employee component, nested subscription eliminated, now using concatMap:

Better Employee Component (employee-concat-map.component.ts)

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { concatMap, Subscription } from 'rxjs';
import { EmployeeService } from './employee.service';

@Component({
  selector: 'employee',
  templateUrl: './employee.component.html',
})
export class EmployeeConcatMapComponent implements OnInit {

  departments: any[];
  name = '';
  status = '';
  deptId = 0;
  departmentsSelect: FormControl;
  employeeForm: FormGroup;
  sub1: Subscription;



  constructor(private _employeeService: EmployeeService) { }


  ngOnInit(): void {

    this.departmentsSelect = new FormControl();

    this.employeeForm = new FormGroup({
      departmentsSelect: this.departmentsSelect
    }
    );



    this.sub1 = this._employeeService.getDepartmentList().pipe(
      concatMap((deptData) => {
        this.departments = deptData;
        return this._employeeService.getEmployeeDetails(123);
        }))
.subscribe(data => {

           // Set selected dropdown value:
           this.employeeForm.controls.departmentsSelect
           .setValue(data.deptId);
           this.status = data.status;
           this.name = data.name;

      }
);
  }
}

So what did we do here? To use the higher order mapping operators, the results of the first observable (get our departments dropdown) aren’t subscribed to directly but “piped” to the concatMap operator. Once getDepartmentList() completes, the result of it (deptData) is assigned to our local variable this.departments which is bound to our select list. Now it returns the results of our second observable (getEmployeeDetails(123)) and that result is what is subscribed to! Once that completes, we can set our correct dropdown value along with the other employee related fields.

This is good code – static code analyzers will like this code. Notice also we only have one subscription to manage now instead of two, since concatMap automatically subscribes and unsubscribes from the inner observable (getEmployeeDetails). We’ll touch on subscription management in another article as well as when you have to unsubscribe from certain observables (which is a topic of much confusion). But for now, you can see how nested observables could lead to many subscriptions whereas we can use just one subscription when using higher order mapping operators. The end result is much cleaner and more durable code.

Now you go read please:


Higher order mapping operators (also has a bit on why nested subscriptions are bad)
https://blog.angular-university.io/rxjs-higher-order-mapping/#:~:text=Angular%20University,-21%20Jan%202022&text=Some%20of%20the%20most%20commonly,%2C%20mergeMap%2C%20concatMap%20and%20exhaustMap.


More on higher order mapping and why nested subscriptions are bad:
https://blogs.msmvps.com/deborahk/higher-order-observable/