So now here we’ll see Angular directives. We’ll try to cover all the important directives. And this is the 6th part of Angular Series. Here is the roadmap of Angular series.
So if you haven’t yet read my previous articles then you can go ahead and start your journey with Angular.
Let’s get started.
We’ve seen ngFor directive. But here we’ll see ngFor in more detail and we’ll explore the other directives as well. So let’s get started.
ngIf
There are times that you may want to show or hide part of the page depending on some condition. So here in app component we’ll define the courses array and here we’ll just use simple numbers. In a real world application, we’ve actual course objects where we define the actual courses. But don’t worry about that and just focus on showing and hiding the part of the page because here we’re using ngIf directive. Now in our app.component.html file, if we’ve courses in the array we’ll render them in a list otherwise we’ll display the message to the user that there are no courses yet.
- export class AppComponent {
- courses = ['MTH', 'CS', 'CHE'];
- }
And here is our app.component.html file,
- <div>
- List of Courses
- </div>
-
- <div>
- No Courses Yet
- </div>
So now here we’ll use the directive to modify the DOM.
There are 2 types of Directives,
- Structural Directives
It modifies the structure of the DOM by adding or removing DOM elements.
- Attribute Directives
They modify the attributes of DOM elements.
Now here we want to change the structure of the DOM. We want to add or remove one DOM element on the basis of condition. When we use structural DOM, we need to prefix them with asterisk (*).
- <div *ngIf="courses.length > 0">
- List of Courses
- </div>
-
- <div *ngIf="courses.length == 0">
- No Courses Yet
- </div>
Now run the application in the browser.
PS C:\Users\Ami Jan\HelloWorld\MyFirstAngularProject> ng serve
Now let’s make the courses array empty in app component. And here we’ll see the result.
- export class AppComponent {
- courses = [];
- }
And this is the classical method of ngIf which we were using prior Angular 4. Now let’s inpect the above image. And here we’ll see just one div.
So the other div isn’t in the DOM. And this is what which I’m trying to show you. So when we use ngIf and the value is true the element will be added to the DOM otherwise it will be removed from the DOM.
Now in Angular 4, here we’ve a slightly different syntax to implement the same feature. So instead of using ngIf twice, we’ll use if else.
- <div *ngIf="courses.length > 0; else noCourses">
- List of Courses
- </div>
- <ng-template #noCourses>
- No Courses Yet
- </ng-template>
So here we’ll use ng-template instead of div and we’ve placed the identifier and we’ll assign this identifier with else in ngIf, Angular 4. Let’s try it in the browser.
And if we place the courses in app component then the result will be.
And now it is working perfectly. But I don’t like this syntax because the first element is div and the second element is ng-template. It's kind of inconsistent in the code. So here we’ve another approach, let’s define another ng-template and this is very simple to understand.
- <div *ngIf="courses.length > 0; then coursesList else noCourses">
- </div>
- <ng-template #coursesList>
- List of Courses
- </ng-template>
- <ng-template #noCourses>
- No Courses Yet
- </ng-template>
So the recommendation is to use this approach because it is clearer and more consistent. But still there is another way to display the conditional block of code.
Hidden Property
Back in the same example, let us see another way to show or hide the part of the page. Instead of using ngIf directive we can use the hidden attribute. And we know in html we can apply hidden attributes to hide them.
- <div hidden>
- List of Courses
- </div>
- <div>
- No Courses Yet
- </div>
And if we see the results now,
Hidden attribute in html also exists as a property in our DOM object. So we can use property binding and bind this property of underlying DOM object to some expression.
- <div [hidden]="courses.length == 0">
- List of Courses
- </div>
- <div [hidden]="courses.length > 0">
- No Courses Yet
- </div>
Now if we go back in our app component, here we have few course elements. Let’s see if application is properly working. And here is the results,
Now let’s make our array empty.
- export class AppComponent {
- courses = [];
- }
Now let’s inspect the text here in the browser.
Look here both divs exist but the first div has a hidden attribute and that’s why it is hidden. So that’s the main difference between using ngIf and the hidden property. When we use ngIf on the element, if the condition evaluates to false, that element will remove from the DOM but when we use the hidden attribute, the element in the DOM is just hidden.
So now you might ask which approach is better? Well if you’re working with a large tree with a lot of children, it's better to use ngIf because these methods take the substantial memory and computing resources. And when these elements are in the DOM, Angular continues to perform change tracking on these elements. So in this situation when you’re dealing with large tree with a lot of objects, it’s better to use ngIf to free up resources.
There is just one exception. In some situations, building a large element tree in the right state may be costly. So if you’ve a page with an element subtree, in that case ngIf may have a negative impact on the performance of that page. So if a user is going to click a button to toggle something to show or hide that part of the page if building the element tree is costly, you should not use ngIf. It’s better to keep in the DOM and use the hidden attribute.
So in summary, if you’re dealing with small tree of objects. It doesn’t matter which approach you choose. It’s purely a personal preference. You’re working with a large tree, first check to see if building that tree is going to be costly or not. If it is costly, use the hidden property to keep in the DOM but hide it. Otherwise it’s better to use ngIf to remove it from the DOM and free up the resources.
ngSwitchCase
In Angular we’ve another directive called ngSwitchCase which is very similar to the concept of switch statement we have in other programming languages. So let’s see how we can implement the ngSwtichCase,
So here is our app.component.html
- <ul class="nav nav-pills">
- <li class="active"><a href="">Grid View</a></li>
- <li><a href="">List View</a></li>
- </ul>
- <div>
- <div>Grid View Content</div>
- <!-- <div>List View Content</div> -->
- </div>
Now let’s make Grid View Content a dynamic div. So let’s open app.component.ts and here we need to define the field to keep track of currently selected tab.
- export class AppComponent {
- view = 'map';
- }
Now back in our template we want to render the div based on the view field value. And view can hold map or list value. Let’s come back to app.component.html and here we are going to apply property binding. So angular adds a new property to our DOM element that is not part of the standard DOM, this property is ngSwitch. We can use property binding ngSwitch and bind this to a field in our class. And on each div we’ll apply ngSwitchCase directive and because with this directive we add or remove a DOM element, this is a structural directive and we should prefix it with asterisk (*)
- <ul class="nav nav-pills">
- <li class="active"><a href="">Grid View</a></li>
- <li><a href="">List View</a></li>
- </ul>
- <div [ngSwitch]="view">
- <div *ngSwitchCase="'map'">Grid View Content</div>
- <div *ngSwitchCase="'list'">List View Content</div>
- <div *ngSwitchDefault>Otherwise</div>
- </div>
If you look at the ngSwitchCase with some more attention, you’ll find map and list value in single quotes and then it is in double quotes. Double quotes for the value of ngSwitchCase directive and single quotes for the value should be in string. ngSwitchDefault for the default value if none of the ngSwitchCase matches then ngSwitchDefault will execute.
Now we want to make a different li active dynamically. If user clicks on Grid View then grid view should be active and if it is list view then list view li should be active.
- <ul class="nav nav-pills">
- <li [class.active]="view == 'map'"><a href="">Grid View</a></li>
- <li [class.active]="view == 'list'"><a href="">List View</a></li>
- </ul>
- <div [ngSwitch]="view">
- <div *ngSwitchCase="'map'">Grid View Content</div>
- <div *ngSwitchCase="'list'">List View Content</div>
- <div *ngSwitchDefault>Otherwise</div>
- </div>
And if we run the application and click on buttons then it is not working properly. It is because we’re not changing the state of the view variable in the component. So let’s call the method on click of anchor tag. But if it is just the single statement of code which we want to write in the method then we can also place it here as well.
- <ul class="nav nav-pills">
- <li [class.active]="view == 'map'"><a (click)="view = 'map'">Grid View</a></li>
- <li [class.active]="view == 'list'"><a (click)="view = 'list'">List View</a></li>
- </ul>
- <div [ngSwitch]="view">
- <div *ngSwitchCase="'map'">Grid View Content</div>
- <div *ngSwitchCase="'list'">List View Content</div>
- <div *ngSwitchDefault>Otherwise</div>
- </div>
Now let’s test the application. And it is working fine.
Now let’s change the value of view variable with something else.
- export class AppComponent {
- view = 'blablabla';
- }
Now save the files and check the output in the browser.
So here is the lesson if you want to compare the value of the field or property against multiple values, use the ngSwitchCase directive.
ngFor
We’ve seen the ngFor directive before. It is used to render the list of objects. Let’s see ngFor directive in more details this time. So let’s define the courses array in app.component.ts
- export class AppComponent {
- courses = [
- {id: 1, name: 'courses1 '},
- {id: 2, name: 'courses2 '},
- {id: 3, name: 'courses3 '},
- ];
- }
Now let’s see ngFor directive to render this array on the view (app.component.html)
- <ul>
- <li *ngFor="let course of courses">
- {{ course.name }}
- </li>
- </ul>
Now let’s see application is working upto this point.
Now this ngFor directive exports a bunch of values that may help you to build certain features; i.e. you may want to render the table and you want to highlight the first row or maybe you want to highlight the last row or you may want to highlight odd or even rows or maybe you want to display the index of each row next to the value then you can use the exported values from ngFor directive
- <ul>
- <li *ngFor="let course of courses; index as i">
- {{ i }} - {{ course.name }}
- </li>
- </ul>
Now let’s test this.
Look before each course, we’ve index in our courses field. So as you can see index of the first item is 0. Index is one of the exported values and there are several others as well. Let me tell you how you can find the list of exported values. Come on to the
angular site
Although we use ngFor in our html directive but the actual name of the directive is ngForOf,
Here D indicates the directive. On click to the directive, on the new page in local variable section we can see all the exported values,
With the help of as keyword we set the alias of the exported value.
Now let’s see one more example, we want to render the table and highlight all the even rows. If you look at the above picture with some more attention then you’ll know that even, odd, first, last are actually boolean properties. So,
- <ul>
- <li *ngFor="let course of courses; even as isEven">
- {{ course.name }} <span *ngIf="isEven">(EVEN)</span>
- </li>
- </ul>
So here is the result of this code.
As our list items are 0 index based, so you’re seeing Even tag on the 1st and 3rd list item.
ngFor and Change Detection
Alright now let’s see how ngFor directive responds to the changes in the component state. Here we want to add the button ‘Add’
- <button (click)="onAdd()">Add</button>
- <ul>
- <li *ngFor="let course of courses">
- {{ course.name }}
- </li>
- </ul>
Now come in to the App component and define the onAdd() method
- export class AppComponent {
- courses = [
- {id: 1, name: 'courses1 '},
- {id: 2, name: 'courses2 '},
- {id: 3, name: 'courses3 '},
- ];
-
- onAdd(){
- this.courses.push({id: 4, name: 'courses4 '});
- }
- }
Now back in the browser a we’ll see the 3 courses initially but when we click on the button, the 4th course is automatically added into ul li.
So Angular has this change detection mechanism. Whenever you click the button or whenever Ajax function or timer request is complete, Angular performs its change detection. It looks at our component, it knows that this course's field now has a new object with id 4. So then it will render that course using ngFor template in app.component.html. Similarly we can add a button next to each course to remove it by passing argument of course object.
- <button (click)="onAdd()">Add</button>
- <ul>
- <li *ngFor="let course of courses">
- {{ course.name }}
- <button (click)="onRemove(course)">Remove</button>
- </li>
- </ul>
Now similarly let’s define the onRemove() method in the component
- export class AppComponent {
- courses = [
- {id: 1, name: 'courses1 '},
- {id: 2, name: 'courses2 '},
- {id: 3, name: 'courses3 '},
- ];
-
- onAdd(){
- this.courses.push({id: 4, name: 'courses4 '});
- }
-
- onRemove(course){
- let index = this.courses.indexOf(course);
- this.courses.splice(index, 1);
- }
- }
Now run the application in the browser and see the results. Randomly delete the record and it will be removed from the in-memory object and from the list in view as well.
So once again after the execution of this onRemove(), Angular performs its change detection. Similarly if we modify any existing object, again Angular will refresh the DOM automatically. So let’s remove the name of this method from onRemove() to onChange()
- <button (click)="onAdd()">Add</button>
- <ul>
- <li *ngFor="let course of courses">
- {{ course.name }}
- <button (click)="onChange(course)">Remove</button>
- </li>
- </ul>
Now rename the method in app.component.ts as well and change the function body as well.
- onChange(course){
- course.name = 'Updated';
- }
And here is the result of the view when I click on the 2nd remove button in li.
Now let’s see the change detection feature from the performance point of view.
ngFor and TrackBy
So once again in app component we’ve courses field with 3 object.
- export class AppComponent {
- courses = [
- {id: 1, name: 'courses1 '},
- {id: 2, name: 'courses2 '},
- {id: 3, name: 'courses3 '},
- ];
- }
Now let’s add the button with click event handler and print courses with ngFor in app.component.html
- <button (click)="loadCourses()">Load Courses</button>
- <ul>
- <li *ngFor="let course of courses">
- {{ course.name }}
- </li>
- </ul>
Now let’s define loadCourses in app.component.ts
- export class AppComponent {
- courses;
-
- loadCourses() {
- this.courses = [
- {id: 1, name: 'courses1 '},
- {id: 2, name: 'courses2 '},
- {id: 3, name: 'courses3 '},
- ];
- }
- }
So the important thing is every time we click the button, we’re resetting this courses field to a new array of objects. Now let’s run the application and inspect the html in the browser.
Look here we can see the 2 browser views before clicking on the button and after clicking on the Load Courses Button. And if we click the Load Courses button again and again then we’ll see the html is highlighted in purple and this purple color means that these html elements are reconstructing again and again.
And it is so fast and it doesn’t have any performance overhead but if you’re working in a large list or in complex markup then sometimes during the lifecycle of your page, you’re going to call backend to download the object. Angular is going to reconstruct this entire DOM object tree and this can be costly if you’re dealing with a large complex list.
Now let’s see how to optimize the code. Angular by default tracks object by their identity. So here we’ve 3 objects and these have 3 different references in the memory.
- export class AppComponent {
- courses;
-
- loadCourses() {
- this.courses = [
- {id: 1, name: 'courses1 '},
- {id: 2, name: 'courses2 '},
- {id: 3, name: 'courses3 '},
- ];
- }
- }
When we reset this courses field, even though we’re dealing with exact same content these objects will be different from the previous ones in the memory. So Angular watches them as a new content and that’s why it reconstructs the DOM tree again and again. Now back in our template, here we’ve the ability to change how Angular tracks objects. So as I said by default it tracks them based on the object identity which means the reference of that object in the memory. So if we redownload the course then it has a different reference identity.
Now we want to instruct Angular to use a different mechanism to track objects, instead by tracking them by their identity or reference in the memory, we should track them by their id. So course with id 1 is always course with id 1. And if we redownload the course's object from the server, none of the properties are changed and Angular will not re-render the DOM element.
- <button (click)="loadCourses()">Load Courses</button>
- <ul>
- <li *ngFor="let course of courses; trackBy: trackCourse">
- {{ course.name }}
- </li>
- </ul>
Here trackCourse is actually the method name and look we’re not calling method here. We’re just referencing it. Now let’s come to the app component and here we’ll define trackCourse() method.
- export class AppComponent {
- courses;
-
- loadCourses() {
- this.courses = [
- {id: 1, name: 'courses1 '},
- {id: 2, name: 'courses2 '},
- {id: 3, name: 'courses3 '},
- ];
- }
-
- trackCourse(index, course) {
- return course ? course.id : undefined;
- }
- }
As we can see we use 2 parameters with trackCourse method and we should use these parameters there. Now let’s run the application. And now click the LoadCourse button again and again and you’ll no longer see the purple color in browser html. It means now Angular doesn’t create the objects again and again.
So here is the lesson if you’re dealing with a simple list, don’t worry about the trackBy feature you really don’t need this. However if you’re dealing with a large list with complex markup and you observe the performance problem on the page then you can try using trackBy to improve the performance of that page. So don’t use it by default on every page because you have to write more code and you won’t gain any performance benefit. So use it only when you need it.
Leading Asterik (*)
So what is this leading asterisk (*) which we’re using with each directive again and again. Well earlier we saw ngIf we else statement but here if we’ve any courses with display them on the screen otherwise we use ng-template to render the no courses msg.
- <div *ngIf="courses.length > 0; else noCourses">
- List of Courses
- </div>
- <ng-template>
- No Courses
- </ng-template>
Now when we use this leading asterisk, we’re telling the angular to rewrite this markup
- <div *ngIf="courses.length > 0; else noCourses">
- List of Courses
- </div>
And it will render in this way, it is going to create ng-template
- <ng-template [ngIf]="courses.length > 0">
- <div>
- List of Courses
- </div>
- </ng-template>
- <ng-template [ngIf]="!(courses.length > 0)">
- No Courses
- </ng-template>
So here is the lesson, whenever we use the ng directive leading with asterisk (*) Angular will be rewrite that block using ng-template. Now obviously we can do this our own but it is much easier to use the leading asterisk and let Angular to do the hard work.
ngClass
So earlier we use star component and here we use class binding twice.
- <span class="glyphicon"
- [class.glyphicon-star]="isSelected"
- [class.glyphicon-star-empty]="!isSelected"
- (click)="onClick()"
- ></span>
While this approach works perfectly fine, there is also another way to deal with classes and you might find this other approach a little bit cleaner. So instead of using class binding twice here, we can use the ngClass directive. So we bind ngClass to an expression and here we’ve an object and this object has one or more key value pairs and each key represents a css class and the value for that key determines if that class should be rendered or not. So here we’re dealing with 2 classes, so we need 2 keys.
- <span class="glyphicon"
- [ngClass]="{
- 'glyphicon-star': isSelected,
- 'glyphicon-star-empty': !isSelected
- }"
- (click)="onClick()"
- ></span>
So if isSelected is true than glyphicon-star will be rendered in the DOM. Keep in mind, put the keys in single quote otherwise it will not work. So with the help of ngClass directive, we don’t have any need to repeat the class binding twice. And we can simply remove the class binding here. Now let’s run the application and obviously it is working fine.
This ngClass is an example of attribute directive, we use it to modify attributes of an existing DOM element.
ngStyle
Now in app component, we have defined a field there.
- export class AppComponent {
- canSave = true;
- }
Let’s go to the template of this component. And here we’ve a button element with 3 style bindings.
- <button
- [style.backgroundColor]="canSave ? 'blue': 'gray'"
- [style.color]="canSave ? 'white': 'black'"
- [style.fontWeight]="canSave ? 'bold': 'normal'"
- >
- Save
- </button>
And for each style binding we have expressions to set the value of the style if canSave is true or false. Now run the application and see the results with canSave true and false to get an idea how the UI looks.
Now to come back to the point, this way of code is little bit noisy. So here we’ll use ngStyle attribute directive. When you’re dealing with multiple style bindings, you may prefer to cleanup your code by using ngStyle directive
- <button
- [ngStyle]="{
- 'backgroundColor': canSave ? 'blue': 'gray',
- 'color': canSave ? 'white': 'black',
- 'fontWeight': canSave ? 'bold': 'normal'
- }"
- >
- Save
- </button>
Now run the application and you’ll see it is working fine as expected.
Obviously, it is still not the best way to implement this feature. If you’re dealing with multiple styles, its better to encapsulate them in a css class and then depending upon the value canSave we render one of these classes instead of adding multiple styles here. But sometimes in certain situations, you may want to add styles explicitly. If that’s the case, use either style binding or all multiple styles altogether.
Safe Traversal Operator
Right now in app component, we have define the field task and it is an object with 2 properties.
- export class AppComponent {
- task = {
- title: 'Review Applications',
- assignee: {
- name: 'John Doe'
- }
- }
- }
Now in app.component.html, we’re using span and interpolation syntax to render the name of assignee of task.
- <span>{{ task.assignee.name }}</span>
And when you go to the browser, you’ll see the result,
Now sometimes when you’re dealing with complex object, it is possible that the value of the property may be null or undefined for certain period of time; i.e., you might want to call different endpoints to get these objects from the server so then assignee might be null. Let’s simulate the scenario,
- export class AppComponent {
- task = {
- title: 'Review Applications',
- assignee: null
- }
- }
And now open the browser and see the result with console message,
So because assignee is null we can’t access the name property of null assignee.
Now there are 2 solutions to solve this problem, one way is to use the ngIf directive.
- <span *ngIf="task.assignee">{{ task.assignee.name }}</span>
Now if you open the browser and you’ll see the browser with no error.
There is also another way to solve this problem, so maybe you want to keep the span in the DOM but you don’t want to render the name of the assignee if it’s null. So here we’ll use Safe Traversal Operator
- <span>{{ task.assignee?.name }}</span>
Now with this syntax, if assignee is null or undefined Angular is going to ignore this otherwise it’s going to render the name property of the assignee on the screen. And again if you come on to the browser, you’ll see no errors. And now if we inspect the browser,
Here we’ve span but nothing inside it. So it was the Safe Traversal Operator which we use when we’re dealing with complex objects.
Custom Directive
There are times that you want to have control over the behavior of DOM elements. Let’s see how to build a simple directive in Angular. Similar to components and services, we can create the directive from scratch or we can use Angular CLI to generate the directive with some boilerplate code. So open up the terminal
PS C:\Users\Ami Jan\HelloWorld\MyFirstAngularProject> ng g d input
g for generate and d for directive, input is the name of directive.
So this creates 2 files: 1 for unit test file and the other one for directive file and upgrade the app.module.ts as well.
Now if you look inside app.module.ts, here we’ve inputDirective in declarations
- @NgModule({
- declarations: [
- AppComponent,
- CoursesComponent,
- SummaryPipe,
- StarComponent,
- PanelComponent,
- LikeComponent,
- InputDirective
- ],
- imports: [
- BrowserModule,
- FormsModule
- ],
- providers: [],
- bootstrap: [AppComponent]
- })
- export class AppModule { }
So as we have discussed before, in this declaration array we should register all the components, all the pipes and all the directives that are part of this module. If you don’t do this, you’ll get the runtime error. Now let’s go back to our inputDirective. It is very similar to component.
- import { Directive } from '@angular/core';
-
- @Directive({
- selector: '[appInput]'
- })
- export class InputDirective {
- constructor() { }
- }
Here we’ve InputDirective typescript class with export and this class is decorated with @Directive decorator function. Here we’ve a selector exactly like components but the selector has square brackets which basically means any elements that has this attribute appInput. If angular finds an element with this attribute, it's going to apply this directive on that element. Now as the best practice, it’s good to prefix your directive, so that they don’t clash with standard html attribute or other directives defined in 3rd party libraries. So here we’ve app as a prefix to appInput directive. Now let’s move on to the actual implementation of this directive. So here we want to handle 2 DOM events (focus and blur).
First of all we need to declare HostListener decorator function from angular/core.
- import { Directive, HostListener } from '@angular/core';
This decorator allows us to subscribe to the events raised from the DOM element that is hosting this directive or in other words the DOM element that has this attribute. So let’s see how can we use HostListener
- import { Directive, HostListener } from '@angular/core';
-
- @Directive({
- selector: '[appInput]'
- })
- export class InputDirective {
-
- @HostListener('focus') onFocus() {
- console.log('on Focus');
- }
-
- @HostListener('blur') onBlur() {
- console.log('on Blur');
- }
- constructor() { }
- }
Let’s test the application up to this point. So let’s go to app.component.html and apply this appInput directive on html element.
- <input type="text" appInput>
So let’s test this. Here we focus and blur the textbox and we got the console messages into the browser console.
Now let’s implement the logic to change the value of the input field to a lowercase string. And we want to apply those changes when the textbox becomes blurred, not in focus. So we remove onFocus() from our component. In OnBlur(), we need to get the value of this input field. First we need a reference of the host element so in our constructor, we need to inject an element reference object
- import { Directive, HostListener, ElementRef } from '@angular/core';
-
- @Directive({
- selector: '[appInput]'
- })
- export class InputDirective {
- constructor(private el: ElementRef) { }
- @HostListener('blur') onBlur() {
- console.log('on Blur');
- }
-
- }
ElementRef is a service defined in Angular that gives us access to a DOM object. So let’s import it on the top. Now this constructor is telling us what are the dependencies of this class.
- import { Directive, HostListener, ElementRef } from '@angular/core';
-
- @Directive({
- selector: '[appInput]'
- })
- export class InputDirective {
- constructor(private el: ElementRef) { }
- @HostListener('blur') onBlur() {
-
-
-
-
-
- let value: string = this.el.nativeElement.value;
- this.el.nativeElement.value = value.toLowerCase();
- }
-
- }
Now let’s test the application in the browser.
Look it is working perfectly when focused out from the textbox. It automatically makes all the letters in lowercase.
Now back in app.component.html, it would be nice to have the flexibility to tell the directive about the target format. Maybe you want to reformat the string as lowercase and somewhere else you want to reformat it as uppercase. So if we had a property-like format then we could use property like property binding.
- <input type="text" appInput [format]="'uppercase'">
Note that here uppercase is in single quotes, that specifies that it is a string not a property of app component. So how do we implement this?
We should already know we’re going to define a field ‘format’ and mark it as Input property. So back in our directive, let’s define a new field and decorate it with Input decorator.
- import { Directive, HostListener, ElementRef, Input } from '@angular/core';
-
- @Directive({
- selector: '[appInput]'
- })
- export class InputDirective {
-
- @Input('format') format;
-
- constructor(private el: ElementRef) { }
-
- @HostListener('blur') onBlur() {
- let value: string = this.el.nativeElement.value;
- if (this.format == 'lowercase') {
- this.el.nativeElement.value = value.toLowerCase();
- }else{
- this.el.nativeElement.value = value.toUpperCase();
- }
- }
- }
So now let’s test the application in the browser. And yes it is working fine.
Now the only issue is, in app.component.html we’ve applied appInput as attribute and then we use property binding to set the target format. Since we’ve only 1 input property here, it would be nicer to set the target format while applying the directive as an attribute.
- <input type="text" [appInput]="'uppercase'">
Now it is much cleaner. But how can we implement this?
Let’s go back to our directive. And all we have to do here is to change the alias of Input directive to the selector of directive
- @Input('appInput') format;
Now back in the browser, let’s test the application in the browser. And yes it is working as we expected. And this is how we simplify the usage of custom directive. And finally we can use the host listener decorator to subscribe to the events raised from the host DOM object.
What Have We Learned?
Now let’s implement something and build something through all the learnings which we’ve covered here.
Now let’s get started by creating a new component.
PS C:\Users\Ami Jan\HelloWorld\MyFirstAngularProject> ng g c zippy
Now open app.component.html and here we want to implement this html code and make it functional.
- <zippy title="Shipping Details">
- Shipping Details Content
- </zippy>
So we come to zippy component. So first of all we change the zippy component selector. And make the title property input property.
- import { Component, Input } from '@angular/core';
-
- @Component({
- selector: 'zippy',
- templateUrl: './zippy.component.html',
- styleUrls: ['./zippy.component.css']
- })
- export class ZippyComponent {
- @Input('title') title: string;
- }
Now let’s go to zippy.component.html
- <div class="zippy">
- <div class="zippy-heading">
- {{ title }}
- </div>
- <div class="zippy-body">
- <ng-content></ng-content>
- </div>
- </div>
Here we’ve prefixed the classes with zippy, it makes our code more maintainable. And if you remember, we use ng-content to dynamically add the html element in the DOM. Now let’s run the application and see how our application looks like.
And this is the result of our views. Now it is the time to style our html elements. So let’s go to zippy.component.css
- .zippy {
- border: 1px solid #ccc;
- border-radius: 2px;
- }
-
- .zippy-heading {
- font-weight: bold;
- padding: 20px;
- cursor: pointer;
- }
-
- .zippy-body {
- padding: 20px;
- }
-
-
- .expanded {
- background: #f0f0f0;
- }
Now back in the zippy.component.html, here we’ll bind expanded class with class binding syntax.
- <div class="zippy">
- <div
- class="zippy-heading"
- [class.expanded]="isExpanded"
- >
- {{ title }}
- </div>
- <div class="zippy-body">
- <ng-content></ng-content>
- </div>
- </div>
Now let’s go back to the zippy.component.ts
- export class ZippyComponent {
- @Input('title') title: string;
-
- isExpanded: boolean = true;
-
- }
Now back in the browser.
And now it is much better. Now we need to implement expanding and collapsing behavior. Now back in the zippy.component.html, here we need to define the click event because on heading div click we need to expand and collapse the divs in the browser.
- <div class="zippy">
- <div
- class="zippy-heading"
- [class.expanded]="isExpanded"
- (click)="onClick()"
- >
- {{ title }}
- </div>
- <div class="zippy-body">
- <ng-content></ng-content>
- </div>
- </div>
So let’s go back and implement the method in component file.
- export class ZippyComponent {
-
- @Input('title') title: string;
- isExpanded: boolean = true;
-
- onClick() {
-
- this.isExpanded = !this.isExpanded;
- }
- }
Now once again back to zippy.component.html, we need to show the body div only if isExpanded is true.
- <div *ngIf="isExpanded" class="zippy-body">
- <ng-content></ng-content>
- </div>
We can use the hidden property as well here which is perfectly fine. Now run the application in the browser and we can see it is working perfectly.
We don’t like this name of the method.
Because when someone sees this method name then he needs to go inside it and look what’s happening there. So we should name the method which is more clear. So make it toggle() in zippy.component.html
- (click)="toggle()"
-
- export class ZippyComponent {
-
- @Input('title') title: string;
-
-
- isExpanded: boolean;
-
- toggle() {
- this.isExpanded = !this.isExpanded;
- }
- }
Now one last thing is remaining, we need to add the glyphicon in zippy.component.html
- <div class="zippy">
- <div
- class="zippy-heading"
- [class.expanded]="isExpanded"
- (click)="toggle()"
- >
- {{ title }}
- <span class="glyphicon"
- [ngClass]="{
- 'glyphicon-chevron-up': isExpanded,
- 'glyphicon-chevron-down': !isExpanded
- }"
- ></span>
- </div>
- <div *ngIf="isExpanded" class="zippy-body">
- <ng-content></ng-content>
- </div>
- </div>
Now come again into the browser and see the results. How our UI looks like,
And now we need to place the chevron right to the div. So let’s go to the zippy.component.css
- .glyphicon {
- float: right;
- }
And now it is perfectly working,
One Last Thing
One last thing I would like to add, sometimes when we start coding, if we’re beginners and we don't have prior experience in that field then we get confused about how to make this complete. Actually this is the time to be brave. Try try again until you succeed. Just divide your tasks into sub tasks and complete them one by one. This is how you can complete any task. But if you think about the complete picture in the beginning then you’ll really be bothered about how you’ll complete it. So don’t think about the future. Just start and you’ll surely achieve it.