To implement a table with paging, sorting, and filtering feature we can use Angular Material. Angular Material is a I Component library that implements Material Design. This UI design language is developed by Google in 2014 which focuses on using grid-based layouts, response animations, transitions, padding and depth effects such as lighting and shadows.
To install Angular Material, follow this;
Open Command Prompt
Navigate to /ClientApp/ folder
Type the following command
> ng add @angular/material
This will trigger the Angular Material command-line setup wizard, which will install following npm packages;
@angular/material
@angular/cdk (prerequisite)
Make sure to install same @angular/material version specified in the package.json.
During the installation process, you will be asked to pick prebuilt theme. I am picking up the first one (Indigo/Pink). Once done the setup process will update the following files;
This JSON is basically a serialization of our entity, with some built-in conventions such as;
CamelCase instead of PascalCase: We got levelName instead of LevelName and son, meaning that all our PascalCase .NET class names and properties will be automatically converted into camelCase when they are serialized to JSO,
No indentation and no line feed / carriage return (LF/CR): Everything is stacked within a single line of text.
These conventions are the default options set by .NET core when dealing with JSON outputs.
To change the default behavior for readability and no PascalCase to CamelCase switching, add these to Program.cs file;
builder.Services.AddControllersWithViews()
.AddJsonOptions(options =>
{
// set this option to TRUE to indent hte JSON output
options.JsonSerializerOptions.WriteIndented = true;
// set this option to NULL to use PascalCase instead of camelCase (default)
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
The story of AngularJS started in 2009 when Miško Hevery (now senior computer scientist and Agile coach at Google) and Adam Abrons (now director of engineering at Grand Rounds) were working on their side project, an end-to-end (E2E) web development tool that would have offered an online JSON storage service and also a client-side library to build web applications depending on it. To publish their project, they took the GetAngular.com hostname.
During that time, Hevery, who was already working at Google, was assigned to the Google Feedback project with two other developers. Together, they wrote more than 17,000 lines of code in 6 months, slowly sinking into a frustrating scenario of code bloat and testing issues. Given the situation, Hevery asked his manager to rewrite the application using GetAngular (the side project mentioned previously), betting that he could do that alone within 2 weeks. His manager accepted and Hevery lost the bet shortly thereafter, as the whole thing took him 3 weeks instead of two; however, the new application had only 1,500 lines of code instead of 17,000. This was more than enough to get Google’s interest for the new framework, which was given the name of AngularJS shortly thereafter.
To listen to the full story, take a look at the following Miško Hevery keynote speech at ng-conf 2014:
This is a continuation of earlier article where we implemented monitoring output as JSON. We are going to build Angular Component that is capable of fetching and displying the JSON data.
Angular component is commonly made of three separate files;
The Component(.ts) file, written in TypeScript and containing the Component class, together with all the module references, functions, variables, and so on.
The Template (.html) file, written in HTML extended with the Angular Template Syntax, which defines the UI layout architecture.
The Style (.css) file, written in CSS and containing the cascading style sheets rules and definitions for drawing the UI.
These three-files approach is the most practical one, the only required file is the Component one, as both the Template and the Style Files could also be embedded as inline elements within the Component file.
If we compare this with Model-View-Controller (MVC) pattern, we can say that, in Angular, The Component is the Controller/ViewModel and the template represents the view.
Navigate through the /ClientApp/src/app folder and create a new health-check folder.
Inside health-check folder, create following files;
Once done, fill-in with these contents;
health-check.component.ts
This one is in /ClientApp/src/app/health-check/health-check.component.ts folder.
At file start, we import all the Angular directives, pipes, services and Components (single word modules) that we need throughout the whole class.
We instantiated a HttpClient service and a baseUrl variable using DI in the Component constructor. The baseUrl value is being set by making use of BASE_URL provider, defined in the /ClientApp/src/main.ts file. This will resolve to our application’s root URL. This value is required by the HttpClient service to build the URL that will be used to fetch the data from the server. (NOTE: I am using .NET CORE Server port to access /hc endpoint becuase Angular runs on proxy side and has no way to see server side except via API. We will use API later).
Last, we defined two interfaces to deal with the JSON request we’re expecting to receive from the HealthChecks middleware; Result and Check.
We need to make following changes to our Program.cs file so that we don’t get CORS issue;
Let’s spend some time on some very important topics in Angular;
Imports and modules
The static import statement is used to import bindings that are exported by other JavaScript modules
A module is basically a collection of variables, functions, classes and so on, grouped within a class: each module is executed within its own scope, not in global scope, meaning all the elements declared within are not visible from the outside unless they are explicitly exported using the export statement. Conversely, to consume a variable, function, class, interface, and so on contained (and exported) within a module, the module has to be imported using the import statement.
This quite similar to what we do with namespaces in most programming languages (C# has using statement, for example).
IMPORTANT
Javascipt modules are different and Angular’s modularity system is different. Angular module are based upon the @NgModule decorator. Each Angular app has at least one NgModule class, called the root module, which is conventionally named AppModule and resides in the app.module.ts file in the application root.
DI
.NET core and Angular make extensive use of this design pattern. Think about dependencies in a class; these can be defined as service or objects that a class needs to instantiate into variables or properties, in order to perform one or more tasks.
In a classic coding pattern, those dependencies are instantiated on the fly within the class itself, for example;
public MyClass() {
var myElement = new Element();
myElement.doStuff();
}
In the preceding example, the myElement variable is an object instance of the Element type, and also a (local) dependency of MyClass: as we can see, it gets instantiated in the constructor because we most likely need to use it there. From there, we can either use it as a local variable (and let it die at the end of the constructor’s lifecycle) or assign it to a class property to further extend its life span and scope.
DI is an alternative software design pattern, in which a class asks for dependencies from external sources rather than creating them itself. To better understand such a concept, let’s try to rewrite the same code as before with a DI approach, like this:
public MyClass(Element myElement) {
myElement.doStuff();
}
As we can see, there’s no need to instantiate the myElement variable because such a task is already handled by the Dependency Injector—an external code that is responsible for creating the injectable objects and injecting them into the classes.
The whole DI coding pattern is based upon the concept of Inversion of Control (IoC), to resolve dependencies. Such a concept revolves around the basic idea that, formally, if ObjectA depends on ObjectB, then ObjectA must not create or import ObjectB directly, but provide a way to inject ObjectB instead. In the preceding code block example, ObjectA is obviously MyClass, while ObjectB is the myElement instance.
ngOnInit (and other lifecycle hooks)
Each Angular Component has a lifecycle, which is managed by Angular. Each time a user visits a view within our app, the Angular framework creates and renders the required Components (and directives) along with their children, reacts to their changes whenever the user interacts with them, and eventually destroys and removes them from the Document Object Model (DOM) when the user navigates elsewhere. All these “key moments” trigger some lifecycle hook methods that Angular exposes to the developers so that they can perform something when each one of them actually occurs: as a matter of fact, they are very similar to the C# event handlers.
ngOnChanges(): Responds when Angular (re)sets data-bound input properties. The method receives a SimpleChanges object of current and previous property values. Called before ngOnInit(), and whenever one or more data-bound input properties changes.
ngOnInit(): Initializes the directive/Component after Angular first displays the data-bound properties and sets the directive/Component’s input properties. Called once, after the first ngOnChanges() method.
ngDoCheck(): Detects and acts upon changes that Angular can’t, or won’t, detect on its own. Called during every change detection run, immediately after ngOnChanges() and ngOnInit().
ngAfterContentInit(): Responds after Angular projects external content into the Component’s view/the view that a directive is in. Called once after the first ngDoCheck() method.
ngAfterContentChecked(): Responds after Angular checks the content projected into the directive/Component. Called after the ngAfterContentInit() method and every subsequent ngDoCheck() method.
ngAfterViewInit(): Responds after Angular initializes the Component’s views and child views/the view that a directive is in. Called once after the first ngAfterContentChecked() method.
ngAfterViewChecked(): Responds after Angular checks the Component’s views and child views/the view that a directive is in. Called after the ngAfterViewInit() method and every subsequent ngAfterContentChecked() method.
ngOnDestroy(): Cleans up just before Angular destroys the directive/Component. Unsubscribes Observables and detaches the event.
A good practice is to use Http-Client source code in the ngOnInit lifecycle method instead of using the Component’s constructor.
Constructor
All TypeScript classes have a constructor() method that will be called whenever we create an instance of that class: since TypeScript is, by all means, a superset of JavaScript, any TypeScript constructor() method will be transpiled into a JavaScript constructor() function. Let’s go with an example;
class MyClass() {
constructor() {
console.log("MyClass has been instantiated");
}
}
This will be transpiled into the following Javascript function;
function MyClass() {
console.log("MyClass has been instantiated");
}
If we omit the constructor in TypeScript, the JavaScript transpiled function will be empty; however, whenever the framework needs to instantiate it, it will still call it in the following way, regardless of whether it has the constructor or not:
var myClassInstance = new MyClass();
Understanding this is very important because it greatly helps us to understand the difference between the Component’s constructor() method and its ngOnInit() lifecycle hook, and it’s a huge difference, at least from the perspective of the Component initialization phase.
The whole Angular Bootstrap process can be split into two major (and subsequent) stages:
Instantiating the Components
Performing change detection
As we can easily guess, the constructor() method is called during the former phase, while all the lifecycle hooks—including the ngOnInit() method—are called throughout the latter.
If we look at these methods from this perspective, it’s pretty easy to understand the following key concepts:
If we need to create or inject some dependencies into an Angular Component, we should use the constructor() method; as a matter of fact, this is also the only way we can do that since the constructor is the only method that gets called in the context of the Angular injector.
Conversely, whenever we need to perform any Component initialization and/or update task—such as performing an HTTP request, updating the DOM, and so on— we should definitely do that by using one of the lifecycle hooks.
The ngOnInit() method, as its name implies, is often a great choice for the Component’s initialization tasks, since it happens right after the directive’s and/or Component’s input properties are set. That’s why we have used this to implement our HTTP request, using the Angular built-in HttpClient service.
HTTPClient
Being able to efficiently send and receive JSON data from our .NET Core Controllers is probably the most important requirement for our single page application (SPA). We chose to do that using the Angular HttpClient service.
HttpClient provides testability support and better error handling via APIs entirely based on Observables.
It’s worth noting that putting the HttpClient service within the Component itself is not good practice because it will often lead to unnecessary code repetitions among the various Components that need to perform HTTP calls and handle their results. This is a known issue that greatly affects production-level apps, which will likely require post-processing of the received data, handling errors, adding retry logic to deal with intermittent connectivity, and so on.
To better deal with those scenarios, it’s strongly advisable to separate the data access logic and the data presentation role by encapsulating the former in a separate service, which can be then injected into all the Components that require it, in a standardized and centralized way.
Observables
Observables are a powerful feature for managing async data; they are the backbone of the ReactiveX JavaScript (RxJS) library, which is one of the Angular required dependencies.
An Observable can be configured to send literal values, structured values, messages, and events, either synchronously or asynchronously: the values can be received by subscribing to the Observable itself using the subscribe method hook, meaning that the whole data flow is handled within it—until we programmatically choose to unsubscribe. The great thing about this approach is that, regardless of the chosen approach (sync or async), streaming frequency, and data type, the programming interface for listening to values and stopping listening is the same.
The great advantages of Observables are the reason why Angular makes extensive use of them when dealing with data. If we take a good look at our HealthCheckComponent source code, we can see how we use them as well when our HttpClient service fetches the data from the server and stores the result in the this.result local variable. Such a task is performed by calling two consecutive methods: get() and subscribe().
Let’s try to summarize what they do, as follows:
get(): As the name suggests, this method issues a standard HTTP request to our .NET Core HealthChecks middleware to fetch the Result JSON response object. This method needs a URL parameter, which we create on the fly by adding the hc literal string (the same string that we set early on, within the Configure method of the Startup.cs file) to the Angular app’s BASE_URL.
subscribe(): This method instantiates an Observable object that will execute two very different actions right after a result and/or in case of an error. Needless to say, all this will be done asynchronously, meaning that it will run in a separate thread (or scheduled for later execution) while the rest of the code continues to execute.
Now that we know how the Angular HttpClient service works, we have every right to ask ourselves a couple of questions: why are we even using those interfaces? Can’t we just use the raw JSON data sent by the .NET Core HealthChecks middleware that we defined early on, consuming them as anonymous JavaScript objects?
Theoretically speaking, we can, just as much as we can output raw JSON from the Controllers, instead of creating all the ViewModel classes like we did instead. In a well-written app, though, we should always resist the temptation to handle raw JSON data and/or to use anonymous objects for a number of good reasons:
We have chosen TypeScript over JavaScript because we want to work with type definitions: Anonymous objects and properties are the exact opposite; they lead to the JavaScript way of doing things, which is something we wanted to avoid in the first place.
Anonymous objects (and their properties) are not easy to validate: We don’t want our data items to be error-prone or forced to deal with missing properties.
Anonymous objects are hardly reusable, and won’t benefit from many Angular handy features —such as object mapping—that require our objects to be actual instances of an interface and/or a type.
The first two arguments are very important, especially if we’re aiming for a production-ready application; no matter how easy our development task might seem at first, we should never think that we can afford to lose that level of control over our application’s source code.
The third reason is also crucial, as long as we want to use Angular to its full extent. If that’s the case, using an undefined array of properties—such as raw JSON data—is basically out of the question; conversely, using a structured TypeScript interface is arguably the most lightweight way to work with structured JSON data in a strongly typed fashion.
It’s worth noting that we’ve not added the export statement to our interface: we did that on purpose since we’re only going to use this within the HealthCheckComponent class. Should we need to change such behavior in the future—for example, to create an external Data Service—we’ll have to add such a statement (and, arguably, move each one of them into a separate file) to enable us to import them into other classes.
health-check.component.html
Located in /ClientApp/src/app/health-check/health-check.component.html folder;
As we can see, the template part of our Angular Component is basically an HTML page, containing a table with some Angular directive. Before moving on, let’s have a closer look, as follows:
ngIf: This is a structural directive that conditionally includes the container HTML element, based on the Boolean expression value specified after the equals (=) sign: when such an expression evaluates to true, Angular renders the element; otherwise, it doesn’t. It can be chained with an else block that—if present—will be shown when the expression evaluates to false or null. In the preceding code block, we use it within the element so that it only appears when the result internal variable (which we defined in the Component class earlier on) stops being null, which will happen after the data has been fetched from the server.
ngFor: Another structural directive that renders a template for each item contained in a given collection. The directive is placed on an element, which becomes the parent of the cloned templates. In the preceding code block, we use it inside the main element to create and show a element (a row) for each check item within the result.checks array.
{{ check.name }}, {{ check.responseTime }}, and so on: These are called interpolations and can be used to incorporate calculated strings into the text between HTML element tags and/or within attribute assignments. In other words, we can use them as placeholders for our class variables’ property values. As we can see, the interpolation default delimiters are the double curly braces, {{ and }}.
health-check.component.css
This file location is /ClientApp/src/app/health-check/health-check.component.css;
There’s not much to say here; just some vanilla CSS to style out the Component template.
Adding the Component to the Angular app
Our Component is ready, we need to add it to our Angular app. In order to do that, we need to make some minimal changes to the following files;
app.module.ts
nav-menu.component.ts
nav-menu.component.html
AppModule
Each new component must be referenced in the AppModule so that it can be registered within our app. On top of that, we need to create the relevant entry within our RoutingModule configuration so that our users wil be able to navigate to that page.
Open the /Client/App/src/app/app.module.ts file and add following highlighted lines;
We are done with the required step of add new component navigation path to the RoutingModule; however we also need to add a link for our users to click;
Open the ClientApp/src/app/nav-menu/nav-menu.component.ts file and add highlighted text;