October 21, 2021

Angular - Validate autocomplete against available options

I used the angular-ng-autocomplete for dropdown lists and its being quite useful and works extremely good with filter.

The issue I faced with validation and it does not behave as per the expectation.

When a user searches for an option by entering the keyword text (but doesn't pick any of the available options), then the required validator failed here. If the input box is empty then the requried validator is working fine on its way. But when the input box has some text, then the requried validator will not trigger. The ng-autocomplete do not have the chance to raise the selected event, and so do not properly set the binded control's value. Since the input box has some value, the form will pass the validation, and when submitted(with null value) it will take the undefined value for the control to the server API.

The html for ng-autocomplete is:

<div class="ng-autocomplete">
	<ng-autocomplete
	  [data]="citiesList"
	  [searchKeyword]="cityName"
	  formControlName="CityId"
	  (selected)="citySelected($event)"
	  (inputChanged)="onChangeCitySearch($event)"
	  [itemTemplate]="itemTemplate"
	  [notFoundTemplate]="notFoundTemplate"
	>
	</ng-autocomplete>

	<ng-template #itemTemplate let-item>
	  <a [innerHTML]="item.Name"></a>
	</ng-template>

	<ng-template #notFoundTemplate let-notFound>
	  <div [innerHTML]="notFound"></div>
	</ng-template>
</div>

In .ts file, I am filling the citiesList from the API:

onChangeCitySearch(search: string) {
   
    //if user has entered at-least 2 characters, then call the api for search
    if (search && search.trim().length >= 2) {
      this.commonDataService
        .getCities(search)
        .subscribe((res) => {
          this.citiesList = res.Data;
        });
    }
  }

I do not want to permit the user to post the form unless one of the suggested options is selected from the list. I fixed the issue by defining a custom validator.

We could have two possible scenarios with ng-autocomplete when validating against a list of options:

  • Array of strings - Available options are defined as an array of strings.
  • Array of objects - Available options are as (an object property i.e. id, name etc, defined on) an array of Objects.

Bind with Array of strings

To validate autocomplete against an array of string options, we can pass the array of options to the the validator, and check if the control's value is exists in the array.

function autocompleteStringValidator(validOptions: Array<string>): ValidatorFn {
  return (control: AbstractControl): { [key: string]: boolean } | null => {
    if (validOptions.indexOf(control.value) !== -1) {
      // null means we dont have to show any error, a valid option is selected
      return null;
    }
	
    //return non-null object, which leads to show the error because the value is invalid
    return { match: false };
  }
}

This is how we can add the validator to the FormControl along with other built-in validators.

public cityControl = new FormControl('', 
    { validators: [Validators.required, autocompleteStringValidator(this.citiesList)] })

Bind with Array of Objects

We can validate the controls value when its binds to an array of objects by using the same technique as above. But I will use a slightly different version, instead of checking the index of input value in the array, here I am using filter method to find the matching item. If it founds any matching record, then the user has properly selected an option from the given list.

function autocompleteObjectValidator(myArray: any[]): ValidatorFn {
    return (control: AbstractControl): { [key: string]: boolean } | null => {
    let selectboxValue = control.value;
    let matchingItem = myArray.filter((x) => x === selectboxValue);

    if (matchingItem.length > 0) {
        // null means we dont have to show any error, a valid option is selected
        return null;
    } else {
        //return non-null object, which leads to show the error because the value is invalid
        return { match: false };
    }
    };
}

The good thing about this technique is that, you can also check for any particular property of the object in the if condition. Lets suppose, if the object has a property Id, we can check if the value of Id is matched on both objects.

let matchingItem = myArray.filter((x) => x.Id === selectboxValue.Id);

Another simpler technique can be applied by checking the type of control.value. For a valid option being selected from the list of objects, its type will be object, and in case the user types the text manully, than the type of control.value will be a simple string. So we can check, if the type is string, then it shows the fact that user has not selected any of the available options from objects list.

function autocompleteObjectValidator(): ValidatorFn {
  return (control: AbstractControl): { [key: string]: boolean } | null => {
    if (typeof control.value === 'string') {
        //return non-null object, which leads to show the error because the value is invalid
        return { match: false };
    }
	
    // null means we dont have to show any error, a valid option is selected
    return null;
  }
}

References:

October 18, 2021

Angular - Lazy-loading feature modules

NgModule is a set of cohesive block of code defines a particular application area, has a closely related set of capabilities. A typical NgModule file declares components, directives, pipes, and services. A module can import functionality from other NgModules that are exported, also a module can also export its own functionality for external use.

Every Angular application has at least one NgModule class which is actually the root module, and conventionally named AppModule. It resides in a file named app.module.ts. The application will be launched by bootstrapping the root NgModule, which actually launch the AppComponent resides in the file app.component.ts.

A small application might have only one NgModule which could be suffice, but as the application grows, we need more feature modules for better maintenance and optimization. So its a good approach to develop your application with multiple modules covering different areas of the application.

One of the main advantages of NgModules is that they can be lazy loaded. Lazy loading is the process of loading components or modules of an application as they're required. In the default application created by Angular, with a single module, all of its components are loaded at once. This means that a lot of unnecessary libraries or modules might be loaded as well, and that could be fine with small applications. But as the application grows, the users will start experiencing performance issues, because the load time will increase if everything is loaded at once. Here we can utilize Lazy loading, which allows Angular to load components and modules only when when they are needed.

Let’s sees an example, how we can configure lazy loading.

In this example, we will create two modules, ModuleUser and ModuleOrder, which will be lazy loaded.

Create a new Angular project (myapp) by executing the below command:

ng new myapp --routing

Here, we are creating a new project with routing.

Open your project in VS Code.

code myapp

By default, a root module(AppModule) is created under /src/app. Below is the content of NgModule(app.module.ts) file that's created.

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

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

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

Typically, it imports all the required modules and components.

The @NgModule decorator states that the AppModule class is a type of NgModule. @NgModule() decorator takes a single metadata object, whose properties describe the module. The most important properties are as follows.

  • declarations: The components, directives, and pipes in this NgModule.
  • imports: Other modules that are required in this NgModule.
  • providers: The services that this NgModule provides to global collection of services; they become accessible in all parts of the application.
  • bootstrap: The root component that Angular inserts into the index.html web page, this component will host all other application views. Only the root NgModule should set the bootstrap property.
  • exports: In above code snippet, we dont have this porperty by default, but if we are defining new modules, we can use this to indicate a subset of declarations that should be visible and usable by other NgModules.

Lets go back to the application, and create two buttons in the app.component.html. Replace your app.component.html file with the contents below.

<button routerLink="user">Load Module User</button>
<button routerLink="order">Load Module Order</button>
<router-outlet></router-outlet>

These buttons will allow the user to load and navigate to corresponding modules.

Let’s define the modules for routes user & order.

To create lazy loaded modules, execute the below commands:

ng generate module moduleuser --route user --module app.module
ng generate module moduleorder --route order --module app.module

The commands will generate two folders called moduleuser and moduleorder. Each folder will contain its own default files, i.e. module.ts, routing.ts and component files.

If you check your app-routing.module.ts you will see the below code for routes:

const routes: Routes = [
  { path: 'user', loadChildren: () => 
           import('./moduleuser/moduleuser.module').then(m => m.ModuleuserModule) },
  { path: 'order', loadChildren: () => 
           import('./moduleorder/moduleorder.module').then(m => m.ModuleorderModule) }
];

For both paths (user & order), it uses loadChildren() function which means when the route user or order is visited, it loads their respective modules.

Run the project with

ng serve

You will see the below screen:

Click the Load Module User button, you will be redirected to the user page. This is how your screen should look:

When you click on Load Module Order button, you should see the similar output with moduleorder's content.

So far, we have create two modules and loaded in our application. But how can we verify if these modules are really being loaded lazily.

In order to verify that these modules files are lazily loaded, open the browser's Developer Tools by pressing F12, visit the Network tab. When you refresh the page it will show a few files that were requested and loaded.

Lets clear your list of requests by hitting the Clear button. Now When you click on the Load Module User button on the page, you will see a request for moduleuser-moduleuser-module.js as in the screenshot below. This verifies that Module User is lazily loaded.

Similarly, when you click Load Module Order, the moduleorder-moduleorder-module.js file is loaded. This verifies that Module Order is loaded lazily.

Once these files are loaded, when you try to click the buttons another time, it will not load these js files again.

References:

October 15, 2021

Angular - Observables vs Subjects vs Behavior Subjects

In this post I will explain different observables with example that would help you to understand what actually is the observable and which type of observable should you use in different scenarios where it makes more sense.

Observables are asynchronous stream of data, emit values over time. It is just a container of values you can subscribe, to receive data when available.

Observables provide support for passing messages between parts of your application. They are used frequently in Angular and are a technique for event handling, asynchronous programming, and handling multiple values.

We have different types of observables avilable in RxJS Library, lets explore each one.

Observable

Basically its just a function, and do not maintain state. Observable will not execute until we subscribe to them using the subscribe() method. It emits three types of notifications, i.e. next, error and complete. Observable code is run for each observer, i.e. if it is making an HTTP request, then that request will be called for each observer/subscribers, hence not a single instance of data is shared, but each obeserver will receive a copy of data. The observer(subscriber) could not assign a value to observable, it can only consume the data.

Lets see an example, first define the observer using the constructor new Observable().

let obs = new Observable((observer) => {
  
  setTimeout(() => { observer.next("1") }, 1000);
  setTimeout(() => { observer.next("2") }, 1000);
  setTimeout(() => { observer.next("3") }, 1000);
  //setTimeout(() => { observer.error("error emitted") }, 1000);    //send error event. observable stops here
  //setTimeout(() => { observer.complete() }, 1000);   //send complete event. observable stops here
  setTimeout(() => { observer.next("4") }, 1000);          //this code is never called, if error or complete event is triggered
  setTimeout(() => { observer.next("5") }, 1000);

})

This observer is emitting values on an interval of 1 second.

There are many operators available with the RxJS library, which makes the task of creating the observable easy. Using these operators you can create observable from an array, string, promise, any iterable, etc. Some of these operators are create, defer, empty, from, fromEvent, interval, of, range, throwError, timer.

Remember that observable will not execute until we subscribe to it. Here we subscribe to all 3 notifications i.e. next, error and complete.

obs.subscribe(
  val=> { console.log('next value received from observer: ' + val) },
  error => { console.log("error event received from observable")},
  () => {console.log("completed event received from observable")}
)

In our Observable definition, we have commented out the lines for error and complete events. Whenver any of these event is triggered, the observable will stop running, so you will not receive any more values if the error or complete event is triggerd on an obeservable.

Subject:

It is technically a sub-type of Observable because Subject is an observable with specific qualities. Subjects are also asynchronous stream of data, emit values. The observer need to subscribe to the Subject in order to receive notifications. The observer would start to receive the values after subscription, and all prior values that might emitted before, would be missed. Observer also have the option to assign value to the observable(Subject). Unlike observable mentioned in previous exmaple, the same code will run for all observers and hence the same data will be shared between all observers.

Lets see an exmaple for Subject,

let subject = new Subject(); 

//subject emits first value, but this will be missed, because till here we have not subscribed to this subject.
subject.next("b");

//first subscription to the subject
subject.subscribe(value => {
  console.log("Subscription received value: ", value); // Subscription wont get anything at this point, the first value "b" emitted above, is missed.
});

//subject emit more values, this time we already have one subscription, so it will receive these values.
subject.next("c"); 
subject.next("d"); 

BehaviorSubject:

It is a special type of Subject, a Subject that requires an initial value and emits its current value to new subscribers. It stores data in memory, and same code run only once for all observers so the same data is shared to all observers. Since it needs an initial value, so it always return a value on subscription even if it hasn't received a next(). Unlike the Subject, once an observer subscribe to it, the observer will immediately receive the current value without having to wait for future next() call.

Lets see an example for BehaviorSubject:

//BehaviorSubject is initialized with initial value "a" through its constructor
let bSubject = new BehaviorSubject("a"); 

//BehaviorSubject emits second value, so the current value here is "b". But still we dont have any subscription.
bSubject.next("b");

//first subscription to the BehaviorSubject, soon we subscribe, the current value "b" will be received.
bSubject.subscribe(value => {
  console.log("Subscription received value: ", value); // Subscription got current value "b", 
});

//BehaviorSubject emit more values, that will be received by our subscription.
bSubject.next("c"); 
bSubject.next("d"); 

ReplaySubject

It is another special type of Subject, a Subject that wil replay the message stream. It stores data in memory, and same code run only once for all observers so the same data is shared to all observers. Unlike the BehaviorSubject, once an observer subscribe to it, the observer will receive the all the previous values that might have emitted before its subscription. No matter when you subscribe the replay subject, you will receive all the broadcasted messages.

Lets see an example:

let rSubject = new ReplaySubject(); 

//ReplaySubject emits three values "a", "b" and "c". But still we dont have any subscription.
rSubject.next("a");
rSubject.next("b");
rSubject.next("c");

rSubject.subscribe(value => {
  console.log("Subscription received value: ", value); // Subscription will get all three values "a", "b" and "c".
});

//ReplaySubject emit more values, that will be received by our subscription.
rSubject.next("d"); 
rSubject.next("e"); 

References: