This is Lab 6 of step by step Angular series. In this article, we will cover two important labs, Pipes and Providers.
Contents
- In the first part, we look into Node, TypeScript, Module loaders/Bundlers and VS Code.
- In the second article, we create a simple basic Angular application with a screen and we run through important concepts like components & modules.
- In the third article, we look into implementing SPA and validations.
- In the fourth article, we understand how to make HTTP calls and how to create custom Angular components using Input and Outputs.
- In the fifth part, we cover two labs, one is how to use JQuery with Angular and Lazy routing.
- In the sixth part, we again cover two labs - Pipes and DI using providers.
In this article, we will cover two important labs, Pipes and Providers. Pipes help us to create reusable code by which we can format outputs and providers help us to create good architecture for Angular.
This lab is the smallest of all labs till now with some theory followed by three steps. So if you are tired and worn out by now, this lab is like a restful lab.
Pipes take in a data input and transform data to a different output. For example, pipe can take “Learn Angular
” as input and transform to “LEARN ANGULAR
” in capital or you can give input as “100
” which you want to display (transform) as “100.00 $
” on UI.
So for this lab, we will do the following pipe transforms: -
- Change
Customer name into capital letters. - Change
Customer amount to proper formatted amount with $ currency sign. - Depending on the
Amount , we would like to display “Gold ” or “Silver ” customer status. So if the amount exceeds 100 , it will display “Gold Customer ” and if less than 100 , it will display “Simple Customer ”.
See the image for more details of how the output would look like.
|
|
Before we move ahead with labs on pipes, let's understand how the syntax of pipes looks like. You need to write pipe syntax inside an expression “{{ }}
”. You need to first put data that needs to be formatted and then followed by “|
” symbol as shown in the below figure. Once you run it, it displays final formatted output on the HTML.
Pipe syntaxes are written inside HTML.
There are two broader categories of pipes: Readymade pipes and Custom pipes. You can get a list of readymade pipes from https://angular.io/api?type=pipe.
So to display “CustomerName
” in capital and “CustomerAmount
” with currency sign, we can use two readymade filters, “uppercase
” and “currency
” respectively. Below are the code syntaxes for the same:
{{CurrentCustomer.CustomerName | uppercase}}
{{CurrentCustomer.CustomerAmount | currency}}
But the third requirement is a bit complicated. In this, if the entered amount is greater than 100
, we need to display “Gold Customer
” and if it’s less than 100
, we need to display “Normal Customer
”.
So let’s create a separate folder called as “Pipes” and in that, let's create “GradePipes.ts” as shown in the below figure:
In “GradePipes.ts”, we will write the code which will have the logic. So the first thing is that we need to import “Pipe
” and “PipeTransform
” from core as these classes are the building blocks for pipes.
import { Pipe, PipeTransform } from '@angular/core';
The next thing is we need to create a “GradePipes
” class which implements the “PipeTransform
” interface and has “@Pipe
” decorated on top of it. Every pipe
class should have a “transform
” method as it is small “t
” “r
”… “transform
” which has an input parameter, i.e., customer amount which will be provided in the UI.
@Pipe({name: 'GradePipes'})
export class GradePipes implements PipeTransform {
transform(value:number): string {
if(value < 100){
return "Simple Customer";
}
else{
return "Gold Customer";
}
}
}
In “@Pipe
” decorator, we have also specified a “name
”. This name will be used in the UI to make a call to the pipe.
And then, finally, we need to call the pipe in the HTML UI. We can now call “GradePipes
” as shown in the below code. “CurrentCustomer.CustomerAmount
” is the input and the pipe is “GradePipes
”.
{{ CurrentCustomer.CustomerAmount|GradePipes}}
Once done, enjoy your output.
Courtesy: https://medium.com/nestle-usa/4-tips-for-starting-your-baby-on-fruits-veggies-aafd6b2c0cde
A sign of a good software architecture is:
“When you make change at one place, you do not need to change at many places”.
Now take the below situation. In your project, let’s say you have lot of modules and 100 (just put a figure 😊) components. Now in all your 100 components, you want to implement logging utility.
In logging utility also, you have different varieties. Some loggers just log to the console of the browser while some display using dialog boxes.
So assume that these loggers are used in those 100s of components to log error and instrumentation messages. So in all your components, you will do two things:
- Import the logger component using “
import
” syntax. - And then create object of the logger.
Below is the code snippet for it.
import { ConsoleLogger} from "../Utility/Utility"
// code deleted for clarity
export class CustomerComponent {
logger: ConsoleLogger = new ConsoleLogger ();
// code removed for clarity
}
Now let’s say somewhere down the line, you think you want to log messages using dialog boxes, think about the amount of changes you need to make in your 100 components.
Let's conclude what is the cause which leads to change in all components. To solve this problem, below is the solution, read the below sentence slowly with full senses alive.
“The problem is because Component is creating the object of Utility, if we can INVERT this, means if we can inject / provide “Utility” to the components rather than creating, then this will make our architecture better”.
In other words, we need to implement “Inversion of control”. Means invert the object creation to someone else and component just refers a generic utility reference.
So rather than components creating the object of the utility, component will ASK for providing/ injecting the “logger
” object via constructor.
IOC is a concept and to implement IOC, we need to use DI. I would encourage you to see this Youtube video which explains DI and IOC in detail.
So now that we know DI and IOC concepts, let’s do a demonstration for the same.
Here is a simple demo we will implement in our project to see the importance of Provider DI. So we will create a simple parent “Logger
” class and we will create two flavours, one which displays messages, one on dialog boxes and other to the console of the browser.
We will then try to figure out how easy it is to make changes at one place and replicate it across all places.
So let’s create a folder called as “Utility” inside which we will create these logger classes.
Let’s create a parent “Logger
” class from which we will inherit and create different flavours of “Logger
” class. Now because this class will be injected in to the components, we need to mark it with “@Injectable()
” decorator. “@Injectable()
” decorator is available in “Injectable
” in “angular/core
”.
import { Injectable } from '@angular/core';
@Injectable()
export class Logger{
public Log(){
}
}
Below is the code for “ConsoleLogger
” and “DialogLogger
” classes which inherit from “Logger
” class. For now, both classes do not have any functionality as such. At this moment, let's focus on DI rather than logging functionality.
@Injectable()
export class ConsoleLogger extends Logger{
public Log(){
}
}
@Injectable()
export class DialogLogger extends Logger{
public Log(){
}
}
So as we discussed previously, the goal is that we want to change at one place and the logger types should change in all components.
Now the whole loading (bootstrapping) of our project starts with “MainModuleLibrary ”, so if we can inject at the level of “MainModuleLibrary ’, it will be available to all components and modules down below.
|
|
In the “MainModuleLibrary
”, using “@NgModule
” decorator, we need to provide the providers which will be trickled down to all components. In providers, we need to specify two things:
- First, the parent class will be referred in all components. So using the “
provide
”, we have specified “Logger
” class. - Second, we need to provide which child implementation object we want to inject in all components. That’s provided in “
useClass
”.
providers: [
{
provide: Logger,
useClass: ConsoleLogger
}
]
The complete code of the main module with “providers
” looks something as shown below:
import {Logger,DialogLogger,ConsoleLogger} from "../Utility/Utility"
@NgModule({
imports: [RouterModule.forRoot(ApplicationRoutes),
HttpModule,
InMemoryWebApiModule.forRoot(CustomerApiService),
BrowserModule,
FormsModule,
ReactiveFormsModule],
declarations: [
MasterPageComponent,
WelcomeComponent],
bootstrap: [MasterPageComponent],
providers: [
{
provide: Logger,
useClass: ConsoleLogger
}
]
})
export class MainModuleLibrary { }
Now that we have define on the main module which object will inject. In the components, we need to expose the “logger
” class in the constructor for DI purpose.
Note: All your components should only refer “Logger
” parent class and not the child classes. You can see in the below code that we have imported only “Logger
” class and through constructor only “logger
” class is referred.
import {Logger} from "../Utility/Utility"
@Component({
templateUrl: "../UI/Customer.html"
})
export class CustomerComponent {
constructor(public http:Http , logger:Logger){
this.Display();
}
}
Run the application put a debug point and see the “ConsoleLogger
” object getting injected. If you change it to “DialogLogger
”, you will see the same injected.
Take a deep breath, close your eyes and think. You are changing in the main module and it's getting propagated all over. As we started this lab saying a “Good architecture is all about changing at one place and the changes are reflected through out”.
For further reading do watch the below interview preparation videos and step by step video series.
- 8th November, 2017: Initial version