Articles in the Series
Introduction
In ASP.NET MVC & Web API - Part 1, we learned basic Angular2 setup in ASP.NET MVC. In this part, we will learn:
- How we can implement search/filter functionality in
UserComponent
to search user by FirstName
, LastName
or Gender
using Angular2 pipe
? - How to implement global error handling by extending
ErrorHandler
class? - How to debug the client code using Firefox debugger?
Let's Start
- First, go through the ASP.NET MVC & Web API - Part 1 and download the attached Angular2MVC_Finish.zip file. Extract it to your desired location in your computer and double click on Angular2MVC.sln to open the solution in Visual Studio (2015 with Update 3 or 2017).
- Since
Angular2MVC
solution doesn’t have the required NuGet
and node_modules
packages, go to Build menu and select Rebuild Solution. Visual Studio will download all the .NET packages listed in packages.config and client packages mentioned in package.json.
- You would find packages folder containing all required .NET packages and node_modules folder containing all client packages:
- Compile and run the application, you shouldn’t receive any error.
- Since all project dependiences are downloaded now, let's start implementing the Search/ filter the data functionality according to the user input, the final output page after filter implementation would be as follows:
- From screenshots, you might have gotten an idea how this search/filter functionality would work, as soon as user would start entering the text in Search textbox, the data would start being filtered on runtime in list after matching the user entered text with First Name, last Name and Gender fields. This is very handy and fast due to the client side filtering. I like this feature because it avoids ugly textbox with search button next to it and server side complex filtering query.
- So, in the next steps, we will learn how to achieve this functionality. We will user Angular2
pipe
to filter the data but before jumping into the code, let’s learn what is the pipe
and how to use it. - Though Angular2 docs have very easy and comprehensive explanation about
pipes
here, for my kind of lazy people, let me summarize it for you. pipe
transforms the data into the meaningful representation, for example, you are getting the date 12/03/2016
from database and want to transform it to Dec 03, 2016
, You could do it through pipe
. Other built-in Pipes
are Uppercase
, lowercase
, json
, etc. that are self-explanatory by their names. Why it is called pipe
, because I think we use |
sign to apply them to the variable or value. E.g. {{Value | uppercase}}
. You can apply as many pipes
as you want to the certain value delimiting by |
sign, e.g., {{ birthdate | date | uppercase }}
. You can also specify the parameter to the pipe
by : (colon)
e.g. the date filter can take format parameter, {{birthdate | date : ‘MM/dd/yyyy’}}
. - Now that we got the basic idea of
pipe
, let’s implement the user search functionality through pipe
. Just like built in pipes
available in Angular2, we can also implement our own custom pipes
, all we need is to implement the PipeTransform
interface and develop custom logic in transform
method that takes two parameters, data
(to be filtered from) and optional arguments
, e.g., user input string
to be searched in the data. To read more about custom pipes
, click here. - Let’s create the
user
filter pipe, right click on the app
folder and select Add -> New Folde
r, name the folder as filter
(or pipe
, whatever you prefer):
- Right click on newly created filter folder and select Add -> TypeScript File:
- Enter the name user.pipe.ts in Item name textbox and click on OK button:
- Paste the following code in newly added user.pipe.ts file:
import { PipeTransform, Pipe } from '@angular/core';
import { IUser } from '../Model/user';
@Pipe({
name: 'userFilter'
})
export class UserFilterPipe implements PipeTransform {
transform(value: IUser[], filter: string): IUser[] {
filter = filter ? filter.toLocaleLowerCase() : null;
return filter ? value.filter((app: IUser) =>
app.FirstName != null && app.FirstName.toLocaleLowerCase().indexOf(filter) != -1
|| app.LastName != null && app.LastName.toLocaleLowerCase().indexOf(filter) != -1
|| app.Gender != null && app.Gender.toLocaleLowerCase().indexOf(filter) != -1
) : value;
}
}
- Let’s understand what we just added in user.pipe.ts:
- In the first line, we are importing the
PipeTransform
and Pipe
interfaces that we are implementing to achieve filtering functionality. - In the second line, we are importing the
IUser
interface that we created in the first part to hold the list of users. Over here, we are also using it to hold the list of users that is the source data for filtering. - In the next line, we are specifying the pipe
selector
/name userFilter
through which we will use the pipe (you will find in future steps, how). - Next, we are creating the
UserFilterPipe
class that is implementing the PipeTransform
interface (implementing interface means providing the body to all methods mentioned in the interface). - Right click on
PipeTransform
and select the option Go To Definition:
- You will be landed to the pipe_transform_d.ts file where you will find the nice brief description how to use the pipe with an example and
transform
method that we must need to implement:
- So let’s go back to user.pipe.ts where can see we have
transform
method with first argument as IUser
array and second is named as filter
that is the input string
to be searched in the IUser
array. - In
transform
method, the first line is only to check if the filter is not null
. - The next statement is the actual implementation of search, if you are a C# developer, you can compare it to the
LINQ to Object
. We are calling Array’s filter
method, checking through conditional operator that if any of IUser
member (FirstName
, LastName
or Gender
) is matching with user input search string
and if YES, returning the filtered result. toLocaleLowerCase
method is converting string
to lower case, to read more about it, click here. If there is no matching record in User list, we are returning all rows.
- Now that we have our filter ready, let’s add it to the
AppModule
to use it in our application, double click on app.module.ts file in app folder to edit it:
- Update the
AppModule
according to the following screenshot:
- We added the
UserFilterPipe
reference by import
statement and in the declaration section. Just for revision, components in declaration
sections know each other, that means, we can use UserFilterPipe
in UserComponent
(or in any other component) without adding the reference in UserComponent
itself. We can declare components
, pipes
, etc. in declaration
section. - So, our user filter/search functionality is ready, the next step is to use it in
UserComponent
but instead of directly using it in UserComponent
, let’s create the shared SearchComponent
that all components can share, this will help us to understand the:
- Interaction between
parent
(UserComponent
) and child
(SearchComponent
) components. - How to send the input parameters through
@Input
and get the value back through @Output
aliases.
- Right click on Shared folder in main app folder and select Add -> TypeScript File:
- Enter the Item name as search.component.ts and click on OK button:
- Copy the following code in search.component.ts file and let’s understand it step by step:
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'search-list',
template: `<div class="form-inline">
<div class="form-group">
<label><h3>{{title}}</h3></label>
</div>
<div class="form-group">
<div class="col-lg-12">
<input class="input-lg" placeholder="Enter any text to filter"
(paste)="getPasteData($event)"
(keyup)="getEachChar($event.target.value)"
type="text" [(ngModel)]="listFilter" />
<img src="../../images/cross.png" class="cross-btn"
(click)="clearFilter()" *ngIf="listFilter"/>
</div>
</div>
<div class="form-group">
<div *ngIf='listFilter'>
<div class="h3 text-muted">Filter by: {{listFilter}}</div>
</div>
</div>
</div> `
})
export class SearchComponent {
listFilter: string;
@Input() title: string;
@Output() change: EventEmitter<string> = new EventEmitter<string>();
getEachChar(value: any) {
this.change.emit(value);
}
clearFilter() {
this.listFilter = null;
this.change.emit(null);
}
getPasteData(value: any) {
let pastedVal = value.clipboardData.getData('text/plain');
this.change.emit(pastedVal);
value.preventDefault();
}
}
- In the first line, we are importing
Input
, Output
interfaces and EventEmitter
class. Input
and Output
interfaces are self-explanatory, to take the input parameter from UserComponent
(in our case, the search string from user), Output
is to send the value back from SearchComponent
but it is little interesting, the output is sent back through event using EventEmitter
class. This will get more clear in the further steps. - In the next line, we are providing the
Component
metadata, i.e., selector
(tag name through which we will use SearchComponent
in UserComponent
, e.g., <search-list></search-list>
). template
is the HTML part of component. You can also put it in a separate HTML file and specify the templateUrl
property instead but since this is quite slim, I would prefer to have it in the same file. - In
SearchComponent
class, we are declaring one local variable listFilter
that is search string
we will use to display here <div class="h3 text-muted">Filter by: {{listFilter}}</div>
. That is only for cosmetic purposes to show what we are searching. - The second variable
title
is with @Input
decorator, we will send search textbox
title from UserComponent
. The third variable change
is with @Output
decorator and of EventEmitter
type. This is how we send data back to parent component. change EventEmitter<string>
means change is an event that parent component needs to subscribe and will get string
argument type. We will explicitly call emit
function (i.e., change.emit(“test”)
) to send the value back to the parent component. getEachChar(value: any)
: This function will be called for every character user will enter in search textbox. We are only calling this.change.emit(value);
that is sending that character to parent component where it is being sent to the UserFilterPipe
pipe to be filtered from User list. Just for revision, in UserPipeFilter
, we are comparing that character with FirstName
, LastName
and Gender
and returning only those records where this character(s) exist. So as long the user would be entering characters in Search textbox, data would be filtering on runtime. clearFilter()
: Will clear the filter to reset the User list to default without any filtering. getPasteData(value: any)
: This is a slightly interesting function that will take care if user would copy search string from somewhere and paste it in search textbox to filter the Users list. Through value.clipboardData.getData('text/plain')
, we are getting the pasted data and sending it through change.emit(value)
function to parent component. - Now, we have some idea about these functions, if you jump back to
SearchComponent template
(HTML). We are calling getEachChar
on keyup
event that will trigger every time user would type in Search textbox, getPasteData
is being called on paste
event that will occur when user would paste value in Search textbox, clearFilter
function would be called on clicking the cross image that would only be visible if search textbox would have at least one character.
- So we are done creating the
SearchComponent
and hopefully you got an idea how it would work, let’s add it in AppModule
so that we can use it, double click on app -> app.module.ts to edit it:
- Add the following
import
statement:
import { SearchComponent } from './Shared/search.component';
- Add
SearchComponent
in declarations section to use it in any component:
declarations: [AppComponent, UserComponent,
HomeComponent, UserFilterPipe, SearchComponent],
- So now our
SearchComponent
is ready to be used, let’s use it in our UserComponent
. Double click on app -> Components -> user.component.html to edit it:
- We will add
SearchComponent
on top of the User list, so append the following div
on top of Add button:
<div>
<search-list [title]='searchTitle' (change)="criteriaChange($event)"></search-list>
</div>
- Let’s understand it, it looks like normal HTML but with
search-list
tags. If you remember, this is the selector
property for SearchComponent
that we defined in search.component.ts file. If you remember in Part 1, we learned about Property Binding [ ]
, that is used to send data from parent component to child component. We are assinging value to child's component title
variable through searchTitle
variable that we defined in UserComponent
. Second is event binding ( )
, we created change
event in SearchComponent
and we are providing the function criteriaChange
in UserComponent
that will execute every time when change event will occur. $event
will hold any value that change
event will send, in our case, we are sending each character user will enter in search text box (refer to getEachChar
function in SearchComponent
). This is how we get value back from child component. - Since we specified
criteriaChange
function in event binding of search-list
, let’s add it in our UserComponent
. Double click on app -> Components -> user.component.ts to edit it:
- Add the following function in user.component.ts:
criteriaChange(value: string): void {
if (value != '[object Event]')
this.listFilter = value;
}
- You can see that we are getting the input parameter value (user entered text in search textbox) from
change
event and assigning it to listFilter
variable that we will use for our pipe
filter. Let’s go ahead and declare listFilter
variable. Add the following line with other variable declaration statements:
listFilter: string;
- So far, we have created the
SearchComponent
that has one textbox
with cross image button next to it to clear the search along read-only display of user search text. In parent UserComponent
, we subscribed the change
event and getting each character of user input in search textbox and assigning it to listFilter
variable where it is getting cumulative (e.g. user enters character 'a
', it would be sent to filter where all records containing 'a
' would be filtered, after 'a
' if user would any other character like 'f
', then both 'a
' and 'f
' would be sent as "af
" to filter and all records with both "af
" combination would be filtered and so on). You will get it once you would start using it or you can debug it that I am explaining in upcoming steps). So, the final step is how to filter the user list according to search text entered in search textbox? So, refresh your pipe knowledge from previous steps and update <tr *ngFor="let user of users">
in app->Components -> user.component.html to <tr *ngFor="let user of users | userFilter:listFilter">
. Where userFilter
is the filter we created in earlier steps and listFilter
is the input parameter to filter. - Since we used
[(ngModel)]
for listFilter
variable for two-way data binding that is defined in FormsModule
, let’s add it in AppModule
, update the:
import { ReactiveFormsModule } from '@angular/forms';
to:
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; in AppModule.
- Add the
formsModule
in imports section.
- Compile and run the project in any browser (Firefox or Chrome is recommended). Go to
UserManagement
Page, right now there may be few records, you can go ahead and add 15, 20 more. Start typing the First Name
, Last Name
or Gender
in Search textbox and you would see records getting filtered in runtime:
- That’s all with our filtering.
- Next, we will learn about Error Handling in Angular2, I will keep it simple and wouldn’t go in every error type but would let you know how you can have custom error for each error type. For quick reference about Angular2
ErrorHandler
class, click here. - For custom error handler class, we can extend
ErrorHandler
class that has constructor
and handleError
method with error
parameter. error parameter has complete error information, e.g., status
, error code
, error text
, etc. depends on error type (HTTP, Application, etc.). that really helps to customize the error message. ErrorHandler
handles any kind of error, e.g., undeclared variable/function, any data exception or HTTP error. Besides ErrorHandler
, I will also explain how you can debug the code in Firefox browser (You can also debug it in Chrome or Internet Explorer). - We will comment out error handling code from
UserService
and UserComponent
so that we can capture all errors in ErrorHandler
class, then we examine the error using Firefox debugger
. So, let’s start. - First of all, let’s create custom error handler class. Right click on app -> Shared folder and select Add -> TypeScript File:
- Enter the name errorhandler.ts and click on OK button:
- Paste the following code in the newly created file:
import { ErrorHandler } from '@angular/core';
export default class AppErrorHandler extends ErrorHandler {
constructor() {
super(true);
}
handleError(error: any) {
debugger;
alert(error);
super.handleError(error);
}
}
- The code is quite self-explanatory with comments, i.e., why we are calling
super(true)
in constructor. AppErrorHandler
is our custom class that is extending Angular2 ErrorHandler
class and implementing the handleError
function. In handleError
, I put debugger to show you what error will come and how you can customize it. We are showing the error message by simple JavaScript alert
function. - First, let’s see the
HTTP
error. Assume we have authentication logic before loading all users from database and request is somehow not authenticated, we will send not authorized (401)
error to Angular2 from ASP.NET Web API. Let’s get this error in AppErrorHandler
and examine it. - Next, add the
AppErrorHandler
in AppModule
to capture the all error. Add the following import
statement:
import AppErrorHandler from './Shared/errorhandler';
- Update the provider section to have
ErrorHandler
:
providers: [{ provide: ErrorHandler, useClass: AppErrorHandler },
{ provide: APP_BASE_HREF, useValue: '/' }, UserService]
- We are telling our module to use our custom error handler for any kind of error. Don’t forget to add the
ErrorHandler
class reference in @angular2/core
import
statement:
- Let’s comment the Error handling in UserComponent.ts file in app -> Components folder. Double click to edit it. Go to
LoadUsers
function and update it as follows:
LoadUsers(): void {
this.indLoading = true;
this._userService.get(Global.BASE_USER_ENDPOINT)
.subscribe(users => { this.users = users; this.indLoading = false; }
);
}
- You can see that I commented out the error statement that was saving in
msg
variable to show at the bottom of screen. - Next, let’s comment the error handling in user.service.ts file, find it in
app ->
Service folder and double click on it to edit. Update the get
method as follows, I commented the catch
statement:
get(url: string): Observable<any> {
return this._http.get(url)
.map((response: Response) => <any>response.json());
}
- Now our client code is ready to capture HTTP exception, let’s add the
unauthorized
exception code in UserAPIController
(Basically, we will add it in BaseAPIController
and call it in UserAPIController
). - Go to Controllers folder and double click on BaseAPIController.cs to edit it:
- Add the following
ErrorJson
function that is actually the copy of ToJson
method but just with Unauthorized
status code (I just created it for sample, you should create more professional error handling code for HTTP calls):
protected HttpResponseMessage ErrorJson(dynamic obj)
{
var response = Request.CreateResponse(HttpStatusCode.Unauthorized);
response.Content = new StringContent
(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");
return response;
}
- Since I do not have any authentication logic so far, so in
UserAPIController
, I am only updating the Get()
method as follows, just replaced the ToJson
function to ErrorJson
, now API will always throw Unauthorized
exception when we will try to load the users:
public HttpResponseMessage Get()
{
return ErrorJson(UserDB.TblUsers.AsEnumerable());
}
- Compile and run the project, go to User Management page. After few moments, you would see an ugly alert message like the following:
- Great, so our test environment is successfully created. We sent this error from
UserAPI
Get()
method to client where it is being captured in our custom AppErrorHandler
. - Let’s debug the error, in errorhandler.ts file, click on gray bar next to
debugger
to setup the break point:
- Run the application in Firefox, press the Ctrl+Shift+S or click on open menu button => Developer => Debugger:
- You should end up with the following screen:
- Go to User Management page, after few moments, you would see the execution gets stopped at
debugger
:
- Mouse hover on
error
and you would see all the parameters in error:
- Since this is an HTTP error, you can see the
HTTPStatusCode
that is 401 (Unauthorized request)
, the body section still has data that definitely you are never going to send back, instead you can send user friendly error message here. - By considering these error parameters, we can extend our error handling by checking the status code. Let’s do it.
- Update the
handleError
as following in errorhandler.ts file:
handleError(error: any) {
debugger;
if (error.status == '401')
alert("You are not logged in, please log in and come back!")
else
alert(error);
super.handleError(error);
}
- Compile
and run
the application again, go to User Management
page again. You would see the following user friendly error message now:
- The Firefox
debugger
is an awesome tool for debugging client code, spend some time exploring more useful features. You can step to next line, into the function or step out by highlighted buttons:
- Next, let’s mess up with our application and check what would be in error variable through Firefox debug. Double click on app => Components => home.component.ts to edit it.
- Enter the following HTML in template section:
<button class="btn btn-primary" (click)="IdontExist()">ErrorButton</button>
- Final template should be as follows:
- I added one button with click event that is calling
IdontExist()
function that doesn’t exist in HomeComponent
. - Let’s run the application and then run the debugger, you would see a stupid
ErrorButton
in the middle of the screen:
- Click on
ErrorButton
, again you would see the execution stopped at debugger
(breakpoint), mouse hover on error, browse the parameter in pop up or click on watch
link at bottom put the error variable on right side Variables
section:
- You can see this whole bunch of new information, expand the
originalError
section and you would see the actual error:
- You can see very detailed information to dig down the complex error.
- Press the Resume button on the left side to continue the execution:
- You would see the brief error message:
- Debugging is a great tool to get complete information on the client side.
History