Displaying a Loading Dialog on an Angular Parent Component While Child Components Load

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

When it comes to components, Angular is one giant dysfunctional family. Parent components constantly trying to control its child component behaviors, child components resenting its parent components and giving them the finger and staying out to all hours, failing out of school and living at home until they are 30.

Wait, that’s not Angular, that’s my family.

ANYWAY, we certainly have to deal with parent components managing child components in Angular. Child components typically load their own data and when you have many child components making their own API calls, populating forms, etc., it would be nice to have a way for the parent to coordinate all that.

A simple use case, but one I find out coming up quite a lot is, I want to display a loading dialog on my parent component, something like “Please wait” or “Loading data” (with some animated something or other) until ALL my child components have loaded their data. In this article I’ll outline a fairly straightforward method I use to do just that and all it uses is output variables and some simple math.

For the “Loading” dialog, I’ll use an Angular overlay. An Angular overlay creates a floating panel and also darkens the rest of the screen to visually indicate to the user, “Just wait a minute Skippy, don’t do anything yet” which I like.

Let’s start by creating a “Loading Data” component to serve as our overlay. We’ll also be using Angular Material in this project (I use it in most projects) as it creates some nice modern looking web components.

Let’s install that first into our project:

>> ng add @angular/material

You’ll be prompted for a few things, just pick a nice color scheme and respond (y)es to any questions. Then we’ll need to modify our app.module.ts to import some items from Angular Material. We’ll need two imports in this case, the mat-card and the progress bar:

import {MatCardModule} from '@angular/material/card';
import {MatProgressBarModule} from '@angular/material/progress-bar';

Then we need to include them in the imports section:

  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatCardModule,
    MatProgressBarModule
  ],

This is the part of Angular Material that can be a bit of a pain, locating where to import the thing you want to use from. Easiest way is to go to this link: https://material.angular.io/components/categories. Then find the thing you’re wanting to use and click on the API tab:


In this case we selected “Button” on the left. The API tab then tells us what import we need to use.

One other item we’ll need to install is the Angular CDK, which stands for component development kit. This library contains several objects that we’ll need for this example. Let’s also install that:

>> npm install @angular/cdk

And again we’ll need to modify our app.module.ts file to import the module and include it in our imports arrray (new code only displayed):

import { OverlayModule } from '@angular/cdk/overlay';
.........
.........
  imports: [
........
    OverlayModule
  ],

Our completed module now looks like this:

App Module (app.module.ts)

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

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { MatCardModule } from '@angular/material/card';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { LoadingDataComponent } from './loading-data/loading-data.component';
import { OverlayModule } from '@angular/cdk/overlay';

@NgModule({
  declarations: [
    AppComponent,
    LoadingDataComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatCardModule,
    MatProgressBarModule,
    OverlayModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

NOTE – If at any time during this exercise you get initializer errors to the effect of LoadingOverlayRef not having an initializer, add this to your “compilerOptions” section in ts.config.json:
“strictPropertyInitialization”: false
I don’t much care for this check, so I turn it off, but I’ll leave a link that explains this setting further at the end of the article, also showing other options.

Now let’s create our Loading Data component that will be used as our overlay:

>> ng g c loading-data

This component will be super simple, just some HTML.

Loading Data HTML (loading-data.component.html)

<mat-card>
    <mat-card-header>
        <mat-card-title class="headerText">
            Loading data...
        </mat-card-title>
    </mat-card-header>
    <mat-card-content>
        <mat-progress-bar mode="indeterminate"></mat-progress-bar>
    </mat-card-content>
</mat-card>

For the guts of this example, we’ll just use App Component though you’d likely be in a custom component in a real project. Let’s start with the very basics of coding an overlay that we can toggle on and off. First, the HTML we’ll use temporarily to test our overlay being displayed then hidden:

App Component HTML (app.component.html)

<button (click)="togglePanel()">Toggle Loading Panel</button>

We’ll have more to do in the HTML file shortly, but for now this button will let us test our loading dialog. Now let’s wire up all the overlay goodness in our App Component:

App Component (app.component.ts)


import { Component, ViewContainerRef } from '@angular/core';
import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { LoadingDataComponent } from './loading-data/loading-data.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'my-children-loading-dialog';
  loadingOverlayRef: OverlayRef;


  constructor(    
    public overlay: Overlay,
    public viewContainerRef: ViewContainerRef
) { 
  
}
  ngOnInit(): void {
  }

  
  openLoadingPanel() {
    const config = new OverlayConfig();

    config.positionStrategy = this.overlay.position()
        .global()
        .centerHorizontally()
        .centerVertically();

    config.hasBackdrop = true;
    this.loadingOverlayRef = this.overlay.create(config);


  this.loadingOverlayRef.attach(new ComponentPortal(LoadingDataComponent, this.viewContainerRef));
  }


  closeLoadingPanel() {
    this.loadingOverlayRef.dispose();
  }


  togglePanel() {

    this.openLoadingPanel();
    setTimeout(() => {
      this.closeLoadingPanel()
    }, 5000);
  }
}

If you’ve never used an overlay, there’s quite a lot going on you haven’t seen before. You’ll be assigned some reading after, but the short of it is we have 4 objects involved in displaying our LoadingDataComponent as an overlay: Overlay, OverlayConfig, OverlayRef, and ViewContainerRef that are manipulated in 2 methods called openLoadingPanel() and closeLoadingPanel().

Firstly, in OpenLoadingPanel(), we create an OverlayConfig that has various options for our dialog:

    const config = new OverlayConfig();

    config.positionStrategy = this.overlay.position()
        .global()
        .centerHorizontally()
        .centerVertically();

    config.hasBackdrop = true;

You can see this config will center our dialog on both axis and use a backdrop that produces a shadow (hasBackdrop) that will disable anything behind it. This is then created in our OverlayRef variable which we smartly named loadingOverlayRef:

this.loadingOverlayRef = this.overlay.create(config);

Then some fanciness that essentially attaches our loading data component to our ViewContainerRef:

this.loadingOverlayRef.attach(new ComponentPortal(LoadingDataComponent, this.viewContainerRef));

Finally a method we’ll use to test the panel called togglePanel(). This will open our loading panel, display it for 5 seconds, then close it:

  togglePanel() {
    this.openLoadingPanel();
    setTimeout(() => {
      this.closeLoadingPanel()
    }, 5000);
  }

So, let’s run the app at this point and click the toggle button. Our panel indeed displays for 5 seconds then closes:

Fantastic. Now that we have the basics in place, let’s return to our original use case – we want to display this panel while several child components load data, and we only want to close it when ALL child components are complete. To that end, let’s create a reusable child component we can embed on the parent page several times. We’ll also use an input variable so we can pass a number of seconds to wait before completing the simulated data load. That way, each child component will take a different amount of time to complete, simulating various API calls in the real world, some taking longer than others. Create a new reusable child component called generic-child:

>> ng g c generic-child 

We’ll just display a string variable in the HTML:

Generic child HTML (generic-child.component.html)

<p>{{ displayText }}</p>

Now I’ll use an input variable in our component file that contains a number of milliseconds to delay displaying a message of ‘Complete!’. Again, this would simulate an API call that doesn’t return instantly:

Generic child component (generic-child.component.ts)

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

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

  @Input() delay = 0;
  displayText = 'Loading Data....'

  ngOnInit(): void {

    setTimeout(() => {
      this.displayText = 'Complete!'
    }, this.delay);

  }

}

So all we’re going to do is use setTimeout() to wait the number of milliseconds passed in, then change the dislayText to complete. Let’s put one of these generic-child components on our parent page to validate the functionality:

App component HTML (app.component.html)

<p>
   <app-generic-child [delay]="5000"></app-generic-child>
</p>

Running our app now displays the text ‘Loading Data….’ for 5 seconds, then changes the display to ‘Complete!’. Let’s add a few more of the generic-child components, each with a different delay. I’ll add 3, but you can add 20, 30, 50 or even 1000 if you’d like! (don’t add 1000).

App component HTML (app.component.html)

<p>
   <app-generic-child [delay]="5000"></app-generic-child>
</p>

<p>
    <app-generic-child [delay]="2000"></app-generic-child>
</p>

<p>
    <app-generic-child [delay]="3000"></app-generic-child>
</p>

As you can see, we now have 3 child components, and they’re all returning the complete message at different times:

Let’s add one last thing to the generic child html file, a simple text box. You can imagine each control might have some user interaction and we don’t want the user to be able to do anything until each instance of generic child loads its data:

Generic child HTML (generic-child.component.html)

<p>{{ displayText }}</p>
Value: <input type = "text" />

As it stands, you can enter data into each text box before the data has completed its simulated load:

So, let’s fix all this and talk about how we’re going to coordinate this in the parent component. I said earlier we’re going to use some output variables and some simple math, and I wasn’t kidding. Let’s add two class variables to the parent:

App Component (app.component.ts)

    private targetNumberOfComponentsToLoad = 3;
    private currentNumberOfComponentsLoaded = 0;

And a method that our child components will call as they finish up their simulated data loads:

  childComplete(caller: number) {
    
    this.currentNumberOfComponentsLoaded += caller;

    if (this.currentNumberOfComponentsLoaded === 
        this.targetNumberOfComponentsToLoad) {
      this.closeLoadingPanel();
    }

  }

Now add the output variable to the generic-child component:

Generic child component (generic-child.component.ts)

@Output() readonly childComplete: EventEmitter<number> = new EventEmitter<number>();

And then emit the event with a value of 1 when the simulated data load is complete:

      ......
      this.displayText = 'Complete!'
      this.childComplete.emit(1);

Our completed child component now looks like this:

Generic child component (generic-child.component.ts)

export class GenericChildComponent implements OnInit {

  @Input() delay = 0;
  @Output() readonly childComplete: EventEmitter<number> = new EventEmitter<number>();

  displayText = 'Loading Data....'

  ngOnInit(): void {

    setTimeout(() => {
      
      this.displayText = 'Complete!'
      this.childComplete.emit(1);

    }, this.delay);

  }

Now we provide the event binding in the output variable in the HTML tag of each child instance much like we did for the property binding in the input variable of ‘delay’:

App component HTML (app.component.html)

<p>
   <app-generic-child [delay]="5000" 
   (childComplete)="childComplete($event)"></app-generic-child>
</p>

<p>
    <app-generic-child [delay]="2000"
   (childComplete)="childComplete($event)"></app-generic-child>
</p>

<p>
    <app-generic-child [delay]="3000" 
   (childComplete)="childComplete($event)"></app-generic-child>
</p>

So each child now can fire the childComplete() method in the parent as their data load completes. But before we run this, let’s make one final change to the parent component:

App component (app.component.ts)

  ngOnInit(): void {
    this.openLoadingPanel();
  }

We’ve added a call to openLoadingPanel() right in the OnInit method because we want the Loading Data panel to display when the component is rendered. Now, as each child completes, they will call childComplete() and increment currentNumberOfComponentsLoaded by 1:

    this.currentNumberOfComponentsLoaded += caller;

And while any child component is waiting to render its data, we are locked out of interacting with any of the form controls, as we intended:

Once all children have finished and currentNumberOfComponentsLoaded equals targetNumberOfComponentsToLoad, closeLoadingPanel() is called:

    if (this.currentNumberOfComponentsLoaded ===
        this.targetNumberOfComponentsToLoad) {
      this.closeLoadingPanel();
    }

And voila, we have a fully populated page of components complete with a Loading Data dialog displayed until all children have finished loading.

In closing, this is a super simple method for a parent component to keep track of child data loads, but this method is great for a few other reasons:

1) Changing the number of components that load is as simple as changing the targetNumberOfComponentsToLoad variable.
Again, if you have 3, 5, 10, 20 components, all you have to do is change the target value and this method works.

2) It works with any level of component nesting
There’s no limit on how many levels of children you can use with this method. If you ever have a case where you have a parent component with children and that component has its own children – no matter the level, a component simply waits until all its child components have checked in, then reports to its parent that it’s complete. That component can then do the same to its parent.

3) It doesn’t just have to be a number; you can get fancy with what you pass back to the parent.
In this case, we’re just passing a “1” back to the parent, but we could also pass something a bit more exotic like a message. You could imagine a case where the parent might have a central area where the status of each load is being reported:

Dashboard Status:

  • Employee data is loading
  • Sales data is complete
  • Forecast data is loading

For your reading pleasure:

Angular CDK and Overlay: https://material.angular.io/cdk/overlay/overview

Inputs and Outputs: https://angular.io/guide/inputs-outputs

Some Notes on the “strictPropertyInitialization” Setting: https://blog.angular.io/angular-cli-strict-mode-c94ba5965f63

Simple Change Detection on Angular Forms

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

We talked about preventing navigation when a form has uncommitted data a few articles back Prevent Navigation from Forms with Uncommitted Data – Angular Love Fest but what’s a good way to detect form changes so we know if we should prevent navigation or not? If you’re ever bored and think “I hope there’s 40 or 50 different ways to accomplish this”, then detecting form changes is a great topic for you to read up on!

However, today I’ll talk about a way I’ve done form change detection in the past and I like it because it’s proven quite effective over time, it’s fairly simple, and doesn’t require a lot of exotic things going on.

While this technique is fairly simple, there are some gotchas you need to be aware of that we’ll code for, so do read the whole thing. First thing we’ll need is, duh, a form. So, let’s make that component:

>> ng g c my-form

And let’s add our HTML to the form:

My Form HTML (my-form.component.html)

<form [formGroup]="myFormGroup">
    <div class="container">
        <div class="row">
            <div class="col-2"><label for="myName">Name</label></div>
            <div class="col-2"><input type="text" id="myName" 
             name="myName" formControlName="myName"></div>
        </div>
        <div class="row mt-2">
            <div class="col-2"><label for="active">Active</label></div>
            <div class="col-2"><input type="checkbox" id="active"
             name="active" formControlName="active"></div>
        </div>
        <div class="row mt-2">
            <div class="col-2"><label for="department">Department</label>
            </div>
            <div class="col-2"><select id="department" 
             name="department" formControlName="department">
                <option value="">-- Please Select --</option>
                <option value="1">Sales</option>
                <option value="2">IT</option>
                <option value="3">HR</option>
                </select>

            </div>
        </div>
        <div class="row mt-2">
            <div class="col-2"><label for="notes">Notes</label></div>
            <div class="col-2"><textarea id="notes" name="notes"
             formControlName="notes"></textarea></div>
        </div>
        <div class="row mt-2">
            <div class="col-2"><button (click)="saveForm()">Save</button>
            </div>
        </div>
    
    </div>

So, we’ve got a textbox, a checkbox, a dropdown and a text area just to cover some of the more common inputs. We also have a button to save the form as we’ll have some things to do in the SaveForm() method. Since the purpose of this article is to demonstrate if any changes in the form have taken place, what we really need as well is a link to navigate off the page. Then we can do some testing to see what happens when a user changes some of the form values and tries to leave the page. Let’s add that link at the very bottom of the above html:

    <div class="container">
        <div class="row mt-4">
            <div class="col-2">
                    <a href="javascript:void(0)"
                     (click)="tryToLeavePage()">Leave page</a>
            </div>
        </div>
    </div>

Now let’s put the basics in our My Form component file so we can run the app:

My Form Component (my-form.component.ts)

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms'


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

  myName: FormControl;
  active: FormControl;
  department: FormControl;
  notes: FormControl;

  myFormGroup: FormGroup;


  constructor() { }

  ngOnInit(): void {

    this.myName = new FormControl('');
    this.active = new FormControl('');
    this.department = new FormControl('');
    this.notes = new FormControl('');

    this.myFormGroup = new FormGroup({
      myName: this.myName,
      active: this.active,
      department: this.department,
      notes: this.notes
    })
  }

  saveForm(): void {
    alert('Saved!');
  }

  tryToLeavePage(): void {
    alert('Leaving page!');
  }

}

So fairly straightforward so far, we’ve created FormControls for our html elements and a FormGroup named myFormGroup. Two simple methods just fire off an alert when we do a simulated save or navigate away from the page.
Our humble little ugly form looks like this now:

So let’s wire up our change detector. And it’s really quite simple provided you code for aforementioned gotchas, which we will of course do. Let’s first add a class variable to our component file:

.......
  initialFormValue = '';
.......

And then we’ll set this value using JSON.stringify in the OnInit. Add this code in that method, right after the form group is built:

.......
    this.initialFormValue = JSON.stringify(this.myFormGroup.value);
    console.log(this.initialFormValue);

So, at very end of our Init, we’re setting initialFormValue to the JSON string representation of our form (and outputting it to the console.) In dev tools we can see what that variable looks like:

{"myName":"","active":"","department":"","notes":""}

As expected, a bunch of empty strings. Now let’s wire up some compare logic in our navigate away method. Replace tryToLeavePage() with the following:

  tryToLeavePage(): void {

    console.log('Initial Form: ' + this.initialFormValue);
    console.log('Current Form: ' + 
                 JSON.stringify(this.myFormGroup.value));

    if (this.initialFormValue === 
        JSON.stringify(this.myFormGroup.value)) {
      alert('Leaving page!');
    } else {
      alert('Form has changed, can\'t let you leave!!');
    }
  }

We’ve got 2 log statements so we can see what’s going on and then the guts of the method which essentially compares the JSON string of the initial FormGroup value to the current value of the FormGroup. If they match (i.e., nothing has changed), we simulate the user being allowed to navigate away from the page. If they don’t equal (some form value has changed), we prevent navigation.

If you click the “Leave page” link without making any changes to the form, you can see navigation is permitted. However, change any value in the form and click “Leave page” again and you’ll see the differences in the JSON strings outputted to the console. So, the very basics of this technique are working.

BONUS: One of my favorite aspects of this method is that if the user changes a value on the form, then changes it back to its previous value, it is NOT reported as a change. This is because we are literally comparing values as opposed to checking if a form control is pristine or dirty.

So, in the method I’m describing here, it’s really as simple as that – comparing 2 JSON strings. However, there’s more to handle to make this work properly and we’ll go over those steps one by one in the next section.

NOTE: If you really want a good example of preventing navigation away from a page that is much more comprehensive than the simple button we’re using here, something you can actually use in your apps to truly prevent navigation, including closing the tab / browser, see the article mentioned above: Prevent Navigation from Forms with Uncommitted Data – Angular Love Fest

Step 1: Account for form initial population

Let’s pretend we grabbed some values from a database when the form is loaded, as the user can come back and edit this form as much as they want. As opposed to writing a service, let’s just use consts for this example to represent the values that were retrieved from the database. In our Init method, right before setting initalFormValue, let’s add variables and setValue statements:

......    
    const myNameFromDb: string = 'John Doe';
    const activeFromDb: boolean = true;
    const departmentFromDb: number = 2;
    const notesFromDb: string = 'Some Notes';
   
    this.myFormGroup.controls.myName.setValue(myNameFromDb);
    this.myFormGroup.controls.active.setValue(activeFromDb);
    this.myFormGroup.controls.department.setValue(departmentFromDb);
    this.myFormGroup.controls.notes.setValue(notesFromDb);

If you now try modifying the form and then clicking to navigate away, everything still works. However, let’s pretend this is a new record and there aren’t any values in the database just yet. (Or perhaps only some form values were saved last time). In that case, we’d pull null from the database as well. So let’s set them all to null to see what happens.

    const myNameFromDb: string = null;
    const activeFromDb: boolean = null;
    const departmentFromDb: string  = null;
    const notesFromDb: string = null;

Now if you navigate away immediately, you’ll get the expected response that nothing has changed. However, go ahead and enter a value in name, then delete it. Also click the checkbox, then unclick it. Now the form should be in the same shape as when it was loaded however, we can see our comparison fails:

Initial Form: {"myName":null,"active":null,"department":null,"notes":null}
Current Form: {"myName":"","active":false,"department":"","notes":null}

So, when we modify a textbox control and then clear it out, the form value is not set back to null, but an empty string. And in the case of the textbox, it’s set to false. So even though conceptually nothing has changed on the form, our comparison indicates quite a lot has changed and prevents navigation – not what we want.

The way to handle this is we must exorcise all nulls from our controls when the data is loaded:

    this.myFormGroup.controls.myName.setValue(
         myNameFromDb === null ? '' : myNameFromDb);
    this.myFormGroup.controls.active.setValue(
         activeFromDb === null ? false : activeFromDb);
    this.myFormGroup.controls.department.setValue(
         departmentFromDb === null ? '' : departmentFromDb);
    this.myFormGroup.controls.notes.setValue(
         notesFromDb === null ? '' : notesFromDb);

NOW, load up the form, change all 4 values, then change them back. As we would expect, we’re now allowed to leave the form as nothing has really changed!

Big Sidebar:

Before we get to the next step, I do need to bring up a possible situation when saving these values to the database. While we’re not really using a database in this example and we’re not going to write any code for our save button in this example, this does need to be mentioned as it might affect your code. That depends 100% on the business rules of your app. Now at the shop I work at, once a user saves a form, if they don’t enter a value into a textbox, it gets saved to the database as an empty string, as conceptually at least, that is the value they saved. And this has never caused any issues for us. If that’s your case, you can skip to Step 2 or read on just in case you ever face this situation. Also, if you have the choice, I would highly recommend going this way.

However, your buiness rules might dictate that if the user saves a form, and hasn’t entered a value or clicked the checkbox that a null value be saved to the database. And that’s just fine. You just have a bit more coding to do in our hypothetical save routine, essentially checking if each control has been “touched” or not, which Angular conveniently exposes for us. Let’s use myName as an example to demonstrate this. Temporarily, change the code of the saveForm to this:

  saveForm(): void {

    const changed = (this.myFormGroup.controls.myName.value !==
                     JSON.parse(this.initialFormValue)['myName']);

    alert(' myName changed? ' + changed + ' myName touched? ' 
         + this.myFormGroup.controls.myName.touched);

    alert('Saved!');
  }

Now load up the form and click save. You’ll see that the control reports as not having been changed and not having been touched, exactly what we’d expect.
Now, enter some text into the name box, then delete it, restoring the myName control to blank. Now click save and you’ll see that changed still reports as false, but touched now reports as true and these are the checks you’d need to make in order to determine if a null should be written to the database:

If a control has no value AND the user hasn’t touched it, you’d record a null to your db.

As to how you’d have to apply this to other controls such as a checkbox is why this method doesn’t make a whole lot of sense to me. Using the same methodology – if a control is blank and the user hasn’t touched it then it’s null:

If a checkbox field in the database has a null value, when that value is loaded, the checkbox is going to show as “false” on the form even though the user hasn’t touched it yet. So, using the above logic, when saved, a null would get written back to the database as the checkbox is still false and it hasn’t been touched.

But what if the user wants it to be false? In this case, they’d have to click it true, so the checkbox control registered as having been touched, but then they’d have to click it again to set it to false! You can imagine how angry a user would be to have to be trained and remember to do that. So, in my opinion, if you have the choice, don’t persist nulls in the database from a form save, but if you must – you know how to check if a user has touched a control.

To continue, we’ll not be saving nulls to our db so restore saveForm() to it’s original simplicity:

  saveForm(): void {
    alert('Saved!');
  }


Step 2: Reset the initialFormValue

Let’s look at the other thing that can bite you in the butt when using this method if you don’t code for it. Load up the form again and change a few values.
Now click Save. The try to leave the page. Well come on, I saved (simulated) the form and it’s still telling me I can’t leave the page!

Now you might have a Save / Submit button that has a redirect at the end of it. However, if you have a Save where the user remains on the page, you’ll need to alter saveForm():

  saveForm(): void {
    this.initialFormValue = JSON.stringify(this.myFormGroup.value);
    alert('Saved!');
  }

Simple fix, but easy enough to miss. The idea being once the user saves the form, the initial values do need to be reset to whatever they saved so the navigation check works properly. Now make a bunch of changes, save the form, then try to navigate away. Victory! We get the behavior we expect. Just one final step which is super simple but also important.

Step 3: Only Reset initialFormValue if the Form is Valid

Our form controls haven’t had any validators up to this point and that would be unusual outside of this example in the real world. Let’s add a validator to name to finish this off:

    this.myName = new FormControl('', Validators.required);

With the validator added, we can now show that you don’t want to reset initialFormValue just any old time the user clicks save but only if the save operation was permitted, i.e., the form was valid and actually saved. So, let’s modify saveForm one final time:

  saveForm(): void {
    if (this.myFormGroup.valid) {
    this.initialFormValue = JSON.stringify(this.myFormGroup.value);
    alert('Saved!');
    } else
    { 
      alert('Form is not valid! (enter a name)');
    }
  }

And now we’re done! You can see by now changing some values in the form, we’re only permitted to leave once save is clicked AND the form is valid, as that’s the only time the initialFormValue is reset:

So hopefully you find this technique useful. There are just a few caveats to code for, but we end up with a pretty simple and fairly durable way to determine if there’s been a form change quickly AND to isolate only true changes to the form.

Read please:

All things Angular forms including form control states, like dirty, touched, and valid: Angular – Building a template-driven form

Passing Arguments by Reference Instead of by Value in Angular

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

Recently this issue came up and I thought it would be a great use case to demonstrate where if we could only pass variables ByRef in TypeScript, our code could be so much cleaner.

In JavaScript (i.e., TypeScript, i.e., Angular), function arguments are almost always passed by value, which essentially means that any variable you pass into a function is passed in as a copy of that variable, not the original variable itself. There are many cases where you could save a bunch of coding if you could only get the method to modify the original variable and not a copy of it. This is called passing by reference.

In this example, I’m going to demonstrate a way to pass function arguments by reference rather than by value and save some coding. So, let’s just jump right into the example.

The hypothetical company we work for is a telemarketer that sells extended car warranties. Did I intentionally pick a type of company that we hate the most? You bet. We can hate the example company and still learn.

Let’s say the salespeople have 3 metrics that are tracked via surveys:

  • Knowledge
  • Courtesy
  • Speed

(In our fantasy world, they sell a LOT of warranties and people even stick around to take the phone survey every time. Pretend is fun).

When the manager pulls up a telemarketer employee, he or she wants to see these metrics for the employee both for the current day and the previous day. In addition, they’d like to display an arrow to indicate the trend of each metric, i.e., if the current day’s rating is more than the previous day’s rating, an up arrow will be displayed to indicate the metric is trending up. Similarly, if the current day’s rating is less than yesterday’s rating, a down arrow should be displayed to indicate the metric is trending down. If the current and previous ratings are the same, a simple dash is to be displayed to indicated there is no trend up or down. One final requirement – if there’s a problem retrieving either piece of data, no trending image should be shown at all.

So for this article, let’s use Font Awesome as it comes with, well, an awesome bunch of icons that look great, many of which of are free.
https://www.npmjs.com/package/@fortawesome/angular-fontawesome

Following the instructions and using the version table they provide; this is what I used to install Font Awesome. I have Angular 13 on the machine I’m using, so I used 0.10.2 for my Font Awesome version:

1 npm install @fortawesome/fontawesome-svg-core
2 npm install @fortawesome/free-solid-svg-icons
3 npm install @fortawesome/angular-fontawesome@0.10.2

If you haven’t played with the Font Awesome icons, you’re missing out on some cool stuff, so added bonus of working through this. Now that we’ve got that set up, let’s create a component for our ratings ByVal page:

>> ng g c ratings-by-val  

We’ll create some mocked calls to an API via a service we’ll create shortly that will return several things:

  • A boatload of employee attributes (simulated)
  • A current and previous value for each of our 3 metrics.

Let’s start with the HTML file to see our UI elements:

Ratings By Val HTML (ratings-by-val.component.html)

<div class="container">
    <div class="row">
        <div class = "col">Knowledge Rating</div>
        <div class = "col">{{ knowledgePreviousRating }}</div>
        <div class = "col">{{ knowledgeCurrentRating }}</div>
        <div class = "col">
            <fa-icon [icon]="faKnowledge" class="{{ knowledgeClass }}"></fa-icon>
        </div>
    </div>
    <div class="row">
        <div class = "col">Courtesy Rating</div>
        <div class = "col">{{ courtesyPreviousRating }}</div>
        <div class = "col">{{ courtesyCurrentRating }}</div>
        <div class = "col">
            <fa-icon [icon]="faCourtesy" class="{{ courtesyClass }}"></fa-icon>
        </div>
    </div>
    <div class="row">
        <div class = "col">Speed Rating</div>
        <div class = "col">{{ speedPreviousRating }}</div>
        <div class = "col">{{ speedCurrentRating }}</div>
        <div class = "col">
            <fa-icon [icon]="faSpeed" class="{{ speedClass }}"></fa-icon>
        </div>
    </div>
</div>

So easy enough – we’ll be displaying a section for each rating that has previous rating, current rating, then the above-mentioned arrow in the [icon] property of the fa-icon tag, binding to a variable in each case (faKnowledge, etc). Also, we have the class of the fa-icon element being bound to a variable – knowledgeClass, speedClass, etc. This is how we’ll display or hide the icon if there’s a “problem” retrieving any data values, supposing our API might not be able to retrieve some data for whatever reason from time to time. In our scss file, we add two classes to handle showing or hiding the icon:

Ratings ByVal style sheet (ratings-by-val.component.scss)

.fa-hide
{
visibility:hidden !important;
}


.fa-display
{
visibility:visible !important;
}

Now we’ll create a service that is a mock API that fetches our data from a database of employee data by employeeId, and returns, as previously mentioned, a boatload of employee attributes (simplified to one field: employeeAttributes), and the current and previous values for our 3 metrics.

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

            employeeAttributes: 'A bunch of employee related attributes',
            knowledgePreviousRating: 8,
            knowledgeCurrentRating: 10,
            courtesyPreviousRating: 12,
            courtesyCurrentRating: 12,
            speedPreviousRating: 7,
            speedCurrentRating: 5

   };
   return of(mockJson);
  }

}

Ok that’s all the setup, so let’s have a look at our component file to hook everything up:

Ratings By Val Component (ratings-by-val.component.ts)

import { Component, OnInit } from '@angular/core';
import { faArrowUp, faArrowDown, faMinus} from '@fortawesome/free-solid-svg-icons';
import { EmployeeService } from '../employee-service';

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


  faArrowUp = faArrowUp;
  faArrowDown = faArrowDown;
  faMinus = faMinus;

  employeeAttributes: string;

  knowledgePreviousRating: number;
  knowledgeCurrentRating: number;
  faKnowledge = faMinus; // set to minus for now
  knowledgeClass = 'fa-hide'; 

  courtesyPreviousRating: number;
  courtesyCurrentRating: number;
  faCourtesy = faMinus; // set to minus for now
  courtesyClass = 'fa-hide'; 

  speedPreviousRating: number;
  speedCurrentRating: number;
  faSpeed = faMinus; // set to minus for now
  speedClass = 'fa-hide'; 


  constructor(private _employeeService: EmployeeService) { }

  ngOnInit(): void {

    this._employeeService.getEmployeeDetails(1234).subscribe((data) =>{

      this.employeeAttributes = data['employeeAttributes'];
      this.knowledgePreviousRating = data['knowledgePreviousRating'];
      this.knowledgeCurrentRating = data['knowledgeCurrentRating'];
      this.courtesyPreviousRating = data['courtesyPreviousRating'];
      this.courtesyCurrentRating = data['courtesyCurrentRating'];
      this.speedPreviousRating = data['speedPreviousRating'];
      this.speedCurrentRating = data['speedCurrentRating'];

      this.assignIcons();


    });
  }

  assignIcons(): void {

    this.knowledgeClass = 'fa-display';

    if ((this.knowledgePreviousRating == null) ||
         (this.knowledgeCurrentRating == null))
    {
      this.knowledgeClass = 'fa-hide'
    } else if (this.knowledgePreviousRating > this.knowledgeCurrentRating) 
    {
      this.faKnowledge = faArrowDown;
    } else if (this.knowledgePreviousRating < this.knowledgeCurrentRating) 
    {
      this.faKnowledge = faArrowUp;
    } else if (this.knowledgePreviousRating === this.knowledgeCurrentRating)         
    {
      this.faKnowledge = faMinus;
    }


    this.courtesyClass = 'fa-display';

    if ((this.courtesyPreviousRating == null) || 
         (this.courtesyCurrentRating == null))
    {
      this.courtesyClass = 'fa-hide'
    } else if (this.courtesyPreviousRating > this.courtesyCurrentRating) {
      this.faCourtesy = faArrowDown;
    } else if (this.courtesyPreviousRating < this.courtesyCurrentRating) {
      this.faCourtesy = faArrowUp;
    } else if (this.courtesyPreviousRating === this.courtesyCurrentRating) 
    {
      this.faCourtesy = faMinus;
    }


    this.speedClass = 'fa-display';

    if ((this.speedPreviousRating == null) || 
         (this.speedCurrentRating == null))
    {
      this.speedClass = 'fa-hide'
    } else if (this.speedPreviousRating > this.speedCurrentRating) {
      this.faSpeed = faArrowDown;
    } else if (this.speedPreviousRating < this.speedCurrentRating) {
      this.faSpeed = faArrowUp;
    } else if (this.speedPreviousRating === this.speedCurrentRating) 
    {
      this.faSpeed = faMinus;
    }
  
  }
}

So a decent chunk of code for sure. Some things you’ll want to notice in the above file: We’re importing the icons we need from font-awesome:

import { faArrowUp, faArrowDown, faMinus} from '@fortawesome/free-solid-svg-icons';

Then we create a class variable for each, named the same:

faArrowUp = faArrowUp;
faArrowDown = faArrowDown;
faMinus = faMinus;

If you hover over these in your code, you’ll notice they are of the IconDefinition type, also from the font-awesome library. In in the OnInit(), we’re assigning the fields returned from the service to our UI, but also note the call to assignIcons() as the final step in our init method:

   this._employeeService.getEmployeeDetails(1234).subscribe((data) =>{

      this.employeeAttributes = data['employeeAttributes'];
      this.knowledgePreviousRating = data['knowledgePreviousRating'];
      this.knowledgeCurrentRating = data['knowledgeCurrentRating'];
      this.courtesyPreviousRating = data['courtesyPreviousRating'];
      this.courtesyCurrentRating = data['courtesyCurrentRating'];
      this.speedPreviousRating = data['speedPreviousRating'];
      this.speedCurrentRating = data['speedCurrentRating'];

      this.assignIcons();

So once our current and previous variables are set, a method called assignIcons() is called to handle which arrow to display (or hide) for each metric. Think what you will about our extended car warranty telemarketing company, but this is an excellent example of a method that would benefit
if we could pass in parameters ByRef (which we will do shortly!)

First though, let’s look at what it does currently:

    this.knowledgeClass = 'fa-display';

    if ((this.knowledgePreviousRating == null) || 
         (this.knowledgeCurrentRating == null))
    {
      this.knowledgeClass = 'fa-hide'
    } else if (this.knowledgePreviousRating > this.knowledgeCurrentRating) 
    {
      this.faKnowledge = faArrowDown;
    } else if (this.knowledgePreviousRating < this.knowledgeCurrentRating) 
    {
      this.faKnowledge = faArrowUp;
    } else if (this.knowledgePreviousRating === this.knowledgeCurrentRating) 
    {
      this.faKnowledge = faMinus;
    }

// rinse / repeat for courtesy metric

// rinse / repeat for speed metric

As you can see, assignIcons() consists of the same block of logic duplicated for each of our 3 metrics and does the following:

1) Sets the class of the icon to fa-display. As the icon is only hidden in one condition (either field is null), this effectively sets the class for the other 3.
2) If either value is null or undefined, sets the class to ‘fa-hide’. Per the requirement, this will hide the arrow if some of the data was unavailable. We’ll test this in a moment.
3) If previous is greater than current, set the icon to the down arrow.
4) If current is greater than previous, set the icon to the up arrow.
5) Finally, if they equal, set the icon to the minus sign (dash).

This is then repeated for the remaining 2 metrics, resulting in quite a bit of duplicated code. (Imagine if there were 5 or 10 metrics!). But you can see this is working as expected in our UI:

You can also test the class swap logic by removing one of the values. Comment out the following line in the service and you’ll see the hiding of the arrow works as well:

// knowledgePreviousRating: 8,

Now we’re simulating a situation where the API was unable to retrieve the knowledge previous rating, and no icon is displayed at all as the ‘fa-hide’ class has been applied to the icon as expected:

So cool, we have functional code. A bit repetitive, but it does work so clearly, we can go home for the week. (We cannot go home for the week).

You might be thinking, this would be much easier if we could modify the data returned, and that’s typically true. However, you might also be stuck using a 3rd party API, which happens a fair amount in the world, and have no control over the shape of the data (or simply an API from another group the in the company that has no desire to change it). The purpose of this article is to demonstrate byVal vs byRef which this example very much does, so let’s make the assumption were using a someone else’s API and we’re stuck with this data format.

Let’s examine the problem a bit deeper by briefly by writing a quick method that demonstrates ByVal behavior. makeIconUpArrow() is a method where we pass an icon in as a parameter and modify it to see what happens. Have a look at this method and let’s, for the moment, add it to our component.ts file:

makeIconUpArrow(icon: IconDefinition): IconDefinition {
  icon = faArrowUp;
  return icon;
}

Our new method simply takes an icon as a parameter, sets that icon to the arrow up, then returns it. Let’s add a call to this method for all 3 icons at the end of onInit() and see what happens. Add these 3 lines after the call to assignIcons() in our init method:

      this.assignIcons();

      // New code
      this.makeIconUpArrow(this.faKnowledge);
      this.makeIconUpArrow(this.faCourtesy);
      this.makeIconUpArrow(this.faSpeed);

So regardless of what icons are assigned to each metrics, we’re just going to try to override that at the end of the OnInit() and assign the up arrow to each icon. The result of this:

And you can see, nothing has changed. Because in Angular, variables are passed byRef. It’s actually a copy of the variable that’s being modified in the makeIconUpArrow() method, not the original icon itself. This is not an Angular issue, nor is it really a TypeScript issue. Since at the end of the day we’re compiling (technically transpiling) to JavaScript, the ByRef / ByVal
behavior has its roots in JavaScript. In JavaScript, primitives (plain old variables) are passed by value. There is no altering that behavior. HOWEVER, and great news for us, Arrays and Objects are passed by reference. So, if the little hamster that spins the wheel that powers your brain is running at full speed and you’re thinking “So if we wrap everything in an object, it will work….”

Correct, that is the solution! (My little hamster broke his leg years ago).

So, let’s make some changes and create a much cleaner method that modifies our icons ByRef. First thing we’ll do is create a Ratings ByRef component:

>> ng g c ratings-by-ref

The one thing we’ll copy over from our previous component is the css styles, so add these 2 styles again:

Ratings ByRef style sheet (ratings-by-ref.component.scss)

.fa-hide
{
visibility:hidden !important;
}


.fa-display
{
visibility:visible !important;
}

Now we’ll build an object, actually an array of objects, for our data so we can pass everything ByRef into our icon logic. First thing we’ll do is create a model which makes life much easier when dealing with objects (yes, I’m guilty of using “any” more than I should in these examples). Our model for rating items:

Rating Item Model (rating-item.ts)

import { IconDefinition } from "@fortawesome/fontawesome-svg-core";

export class RatingItem {
    ratingName: string;
    previousRating: number;
    currentRating: number;
    icon: IconDefinition;
    class: string
}

Next we’ll create the Ratings ByRef component file. We’ll both import that class and create a new variable called ratingGroup. This will hold an array of RatingItem objects. In our Init method, we’ll now build this array of RatingItems from the data retrieved from the service:

Ratings ByRef Component (ratings-by-ref.component.ts)

import { Component, OnInit } from '@angular/core';
import { faArrowUp, faArrowDown, faMinus } from '@fortawesome/free-solid-svg-icons';
import { EmployeeService } from '../employee-service';
import { RatingItem } from '../rating-item';

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


  faArrowUp = faArrowUp;
  faArrowDown = faArrowDown;
  faMinus = faMinus;

  employeeAttributes: string;

  ratingGroup: RatingItem[] = [];


  constructor(private _employeeService: EmployeeService) { }

  ngOnInit(): void {

    this._employeeService.getEmployeeDetails(1234).subscribe((data) => {

      const riKnowledge: RatingItem = {
        ratingName: 'Knowledge',
        previousRating: data['knowledgePreviousRating'],
        currentRating: data['knowledgeCurrentRating'],
        icon: faMinus,
        class: 'fa-hide'
      };

      const riCourtesy: RatingItem = {
        ratingName: 'Courtesy',
        previousRating: data['courtesyPreviousRating'],
        currentRating: data['courtesyCurrentRating'],
        icon: faMinus,
        class: 'fa-hide'
      };

      const riSpeed: RatingItem = {
        ratingName: 'Speed',
        previousRating: data['speedPreviousRating'],
        currentRating: data['speedCurrentRating'],
        icon: faMinus,
        class: 'fa-hide'
      };

      this.ratingGroup.push(riKnowledge);
      this.ratingGroup.push(riCourtesy);
      this.ratingGroup.push(riSpeed);


      this.assignIcons();



    });
  }


  assignIcons(): void {

  }

}

Now we have an array of the 3 groups of data points for our UI, with some placeholder values for icon and class which will get changed by our assignIcons() method, which is blank for the moment, and we’ll fill in shortly.
First let’s look at our new html file, wired up to our new array of objects:

Ratings ByRef HTML (ratings-by-ref.component.html)

<div class="container">
    <div class="row">
        <div class = "col">ratingGroup[0]['ratingName']</div>
        <div class = "col">{{ ratingGroup[0]['previousRating'] }}</div>
        <div class = "col">{{ ratingGroup[0]['currentRating'] }}</div>
        <div class = "col">
            <fa-icon [icon]="ratingGroup[0]['icon']" 
             class="{{ ratingGroup[0]['class'] }}"></fa-icon>
        </div>
    </div>
    <div class="row">
        <div class = "col">ratingGroup[1]['ratingName']</div>
        <div class = "col">{{ ratingGroup[1]['previousRating'] }}</div>
        <div class = "col">{{ ratingGroup[1]['currentRating'] }}</div>
        <div class = "col">
            <fa-icon [icon]="ratingGroup[1]['icon']" 
             class="{{ ratingGroup[1]['class'] }}"></fa-icon>
        </div>
    </div>
    <div class="row">
        <div class = "col">ratingGroup[2]['ratingName']</div>
        <div class = "col">{{ ratingGroup[2]['previousRating'] }}</div>
        <div class = "col">{{ ratingGroup[2]['currentRating'] }}</div>
        <div class = "col">
            <fa-icon [icon]="ratingGroup[2]['icon']" 
             class="{{ ratingGroup[2]['class'] }}"></fa-icon>
        </div>
    </div>
</div>

This is similar to our previous html file; however, our values are now bound to the appropriate item in the array of objects we are creating now. If we start our app and have a look at the current output, we see that everything is bound correctly in the UI, however our icon logic isn’t working yet as expected since the assignIcons() method is empty at the moment:

Now let’s add the guts to our new assignIcons() method and have a look. First alter the final line in the Init method, modify the call to assignIcons() to pass in (ByRef!) our ratingGroup array of objects:

  this.assignIcons(this.ratingGroup);

Then replace the empty assingIcons() method with the actual code:

  assignIcons(rg: RatingItem[]): void {

       rg.forEach(item => {
  
          item['class'] = 'fa-display';

          if ((item['previousRating'] == null) 
            || (item['currentRating'] == null)) 
          {
            item['class']= 'fa-hide';
          } else if (item['previousRating'] > item['currentRating']) {
            item['icon'] = faArrowDown;
          } else if (item['previousRating'] < item['currentRating']) {
            item['icon'] = faArrowUp;
          } else if (item['previousRating'] === item['currentRating']) {
            item['icon'] = faMinus;
          }
    
        });

  }

Now that’s a method you can show to your children with pride. As you can see, since we’re working with an object, everything is now passed byRef to this method, so we simply loop through the array of objects. assignIcons() can loop through 5, 10, 100 ratings no problem:

Look at that, a winner is you!

So, in summary, while it would be great if in TS / JS we could simply specify byRef or byVal on method parameters, with some creative use of Arrays and Objects, we can achieve the same result.

For your reading:

All you care to know about JavaScript objects: Working with objects – JavaScript | MDN (mozilla.org)

Prevent Navigation from Angular Forms with Uncommitted Data

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

Problem: My users are stupid and keep navigating away from a form with changed or dirty data or closing the browser altogether then complain because none of their changes are showing up.

Your users are not stupid, they’re just busy with jobs that aren’t writing software. We all face this common problem with end users. They either:

  • navigate away from a form with uncommitted / dirty data.
  • close the browser or tab when a form has uncommitted / dirty data.

Typically, this is followed by blaming the app for not saving their changes. While we have some options here, such as forms that save every so often,
today we’ll look at a pretty typical solution – a simple confirmation prior to navigation or closing the browser / tab. There are several approaches to this, but the following is how I’ve implemented this several times and I’ve found it to be pretty durable. If you have a different approach, feel free to share it or improve upon this one.

We’ll need to hit this from two angles, using an Angular route guard for in app navigation and then hooking to a DOM event in case the user tries to close a tab or the entire window.

NOTE – If you’re using lazy loading with your modules (as we do where I work), there’s some changes you’ll need to make which I’ll go over at the very end of the article.

So let’s get started – we’ll create 2 components in our app, one that will utilize the dirty form logic, the other will be a separate route so we can see the navigation behavior in action:

>> ng g c dirty-form
>> ng g c other-route

Now we’ll need to set up a route to each component:

(app-routing.module.ts)

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DirtyFormComponent } from './dirty-form/dirty-form.component';
import { OtherRouteComponent } from './other-route/other-route.component';

const routes: Routes = [
  {
    path: 'DirtyForm',
    component: DirtyFormComponent,
  },
  {
      path: 'OtherRoute',
      component: OtherRouteComponent      
  }
];

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

Ok, super simple, we have an app with 2 components and the ability to route between them. Let’s do some modifications to our dirty-form.component files.

Dirty Form HTML (dirty-form.component.html)

<p>Form goes here.</p>

Amazing levels of realism I know, but in actuality we don’t need a form to demonstrate what we’re doing here, just a placeholder.


Earlier I stated we’d need to attack this from two angles, and for both we’ll need an Angular route guard. Route guards, if you’ve never used one are interfaces provided by Angular that allow us to control access to our routes. There are several types which you will read up on at the end of the article. For our purpose, thankfully Angular provides just the type of route guard we need called CanDeativate. Let’s create that very thing in a new file:

Route Guard (dirty-form-guard.ts)

import { CanDeactivate } from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

export interface ComponentCanDeactivate {
  canDeactivate: () => boolean | Observable<boolean>;
}

@Injectable()
export class DirtyFormGuard implements CanDeactivate<ComponentCanDeactivate> {
  canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
    return component.canDeactivate() ? true :
      confirm('WARNING: You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes.');
  }
}

End result of this – we’ll be able to use a boolean (say formUnchanged or something of the like) to determine if a user can navigate away from a page or close the browser window. This is powerful stuff and makes it very easy to implement on every form in our app. Total win.

So how do we use this newfound power? First, let’s make sure we provide it in our app module:

App Module (app.module.ts)

....
  providers: [DirtyFormGuard],
...

And now in our dirty form component file, let’s wire up the guard:

Dirty Form Component (dirty-form.component.ts)


import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { ComponentCanDeactivate } from '../dirty-form-guard';

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

  formUnchanged = false;

  public canDeactivate(): Observable<boolean> | boolean {
    
    return this.formUnchanged;

  }


  constructor() { }

  ngOnInit(): void {
  }

}

So 2 things in our DirtyFormComponent, notice we implement the interface from our route guard, ComponentCanDeactivate:

export class DirtyFormComponent implements OnInit, ComponentCanDeactivate {

This is good practice and ensures that we provide the guts for (or an implementation of) the required method canDeactivate():


  public canDeactivate(): Observable<boolean> | boolean {
    
    return this.formUnchanged;

  }

Now in this case we are doing something very simple – we have a variable called formUnchanged (set to false) as a placeholder to indicate that a user has made a change to the form on the page.

This is in the interest of sanity. I’d prefer a series of smaller articles that address a single need rather than something that’s 1700 pages long. The reason being is HOW to tell if a user has truly made a change to a form is an entire discussion in itself (which everyone seems to have their own approach to)
and will be covered in its own article which I will link to here: Simple Change Detection on Angular Forms – Angular Love Fest


Give that a good read when you have some time, but for now let’s simply concentrate on the topic at hand of how to prevent navigation when the form has changed and for that purpose – our little boolean will do just fine.

So our final step in preventing in app routing is to assign this route guard to whatever components we care to. In this case, we’ll attach it to our dirty form component in our app routing file:


................
  {
    path: 'DirtyForm',
    component: DirtyFormComponent,
    canDeactivate: [DirtyFormGuard]
  }
...............

I mentioned earlier that we can use this logic in every form in our app and you can see how easy it is to use this guard on whatever routes we want. Let’s see what happens now when we start our app and try to navigate away from our dirty form component:

Fabulous! You can see anytime we click on the “Dirty Form” route then try to navigate to “Other Route”, our confirmation box fires and forces us to hit ok to proceed (or cancel to stay). Also note if we set our formUnchanged variable to true in the dirty form component, this simulates a user having made no changes to the form and navigation proceeds without any prompting.

So that’s the first part of the battle – note that you can still close the tab or close the browser entirely and the prompt doesn’t occur, leaving a way for our users to lose uncommitted data. Closing the page / tab is a bit tricker (but not super difficult) to handle and for that we’ll have to hook into the DOM of the web browser. Fortunately, angular makes this very straightforward.

Begins yelling at cloud
If you don’t know what the DOM (document object model) is, you’re likely to have not been doing much web programming in the 90s. Back when I had to walk uphill to school both ways and the only game on my phone was Snake, we had to deal with DOM of various web browsers…
End yelling at cloud moment

tl;dr – there’s objects and events in your web browser itself that, even though we are very much shielded from them these days thanks to all the wonderful JavaScript frameworks, we still need to access once in a while. And I said Angular makes that very straightforward and indeed they do with a decorator called @HostListener.

From Angualr.io: (@HostListener) Decorator that declares a DOM event to listen for and provides a handler method to run when that event occurs.

EXACTLY what we need and the DOM event in question that we want to listen for is ‘window:beforeunload’:

From Mozilla: The beforeunload event is fired when the window, the document and its resources are about to be unloaded.

So let’s listen for that DOM event and provide a method to run when that event occurs. Make this simple change in dirty-form.component.ts by adding the @HostListener decorator before our canDeactivate() method:

Dirty Form Component (dirty-form.component.ts)

  @HostListener('window:beforeunload')
  public canDeactivate(): Observable<boolean> | boolean {
    
    return this.formUnchanged;

  }

Now, back in our app, in addition to getting our confirmation prompt when we try to navigate away from the dirty form page, if we try to close the tab or the browser itself:

Now we’ve got our users right where we want them! (not really but count the small victories) If we determine there is uncommitted data, we can now both prevent navigating away from the page and prevent closing the tab or browser without confirmation.

NOTE – You’ll note the message is different when they try to close the browser. Why is that? The short answer – most modern browsers don’t support custom messages in the beforeunload popup. I don’t find this very troubling as the message is pretty clear. You can also copy this message and use it in your canDeativate method if you need them to be consistent.

IF YOU’RE USING LAZY LOADED MODULES
We use lazy loaded modules where I work and I had real problems making this work at first, it just wouldn’t work, or I’d get null component error.
Let me save you the hair pulling on this one – it’s mentioned here in this issue at the Angular github:
https://github.com/angular/angular/issues/16868 (About 1/3 of the way down – picninim commented on Sep 20, 2017 – explains it).


Luckily, it’s not a difficult change, but if you’re using lazy loaded modules you need to move the canDeactive from the app-routing.module.ts to the lazy loaded modules’ routing file, in our example, if we used lazy loaded modules, that would be in the file dirty-form-routing.module.ts:

Dirty Form Routing Module (dirty-form-routing.module.ts)

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DirtyFormGuard } from '../dirty-form-guard';
import { DirtyFormComponent } from './dirty-form.component';

export const CHILD_ROUTES: Routes = [{

  path: '',
  component: DirtyFormComponent,
  canDeactivate: [DirtyFormGuard]
}];

 
@NgModule({
  imports: [
    RouterModule.forChild(CHILD_ROUTES)

  ],

  declarations: [],
  exports: [
    RouterModule
  ]

})

export class DirtyFormRoutingModule { }

And you’d follow the above pattern for each lazy loaded module.

You go read now:

Angular route guards
https://codeburst.io/understanding-angular-guards-347b452e1892

Host listener
https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event

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/