Want to dress up the UI in your Angular app?

You can do that with custom attribute directives.

And in this guide, I'll show you how.

What Are They?

First of all: what the heck are attribute directives?

Well if you go back to your Angular 101 days, they probably taught you that there are two types of directives: attribute directives and structural directives.

Okay, well there are also components. But let's leave those aside for right now.

Structural directives change the Document Object Model (DOM) layout. Examples include *ngIf and *ngFor.

Attribute directives, on the other hand, change the appearance or behavior of an element.

So if you're looking to make things pretty, you should consider using an attribute directive.

What's Wrong With CSS?

At this point you might be asking yourself: "Why would I use an attribute directive to make things look pretty when I can use CSS?"

And that's a very good question.

The answer is: because sometimes the presentation is tied to your object model. And CSS isn't aware of the underlying object model by itself. 

When you need to create a UI element based on the state of the model, you'll often find that directives are the way to go.

But What About Binding?

If you're already a sophisticated Angular developer, you might be thinking to yourself: "What about binding? Can't I connect the object state to the presentation with binding?"

And the answer is yes. You can.

So why directives? To answer that question, let's look at an example.

Suppose I've got a CRM application. I want to show users a list of recent activites and the outcome/status of each activity.

The output will look something like this:

 

See those indicators in the last two rows? One says "On Hold" while the other says "Interested."

But they display a background color based on the status or outcome. The "On Hold" indicator displays with a yellowish background while the "Interested" indicator has a green background.

Now if I go the binding route to make that happen, the code would look something like this:

<div *ngIf="row.outcome" class="badge"
    [class.badge-warning]="row.outcome.name=='On Hold'"
    [class.badge-error]="row.outcome.name=='Cancelled'"
    [class.badge-success]="row.outcome.name=='Completed'">

And that would work. But it creates a bit of clutter in the HTML, don't you think?

The problem gets worse, though.

That element above displays the outcome of the activity. But if it's a task with a status, I'd have to add something similar to that in another element with a different *ngIf outcome.

And again: it would work. But it's fugly.

This would look much better:

<div *ngIf="row.outcome" [displayOutcomeStatus]="row">

That's what you can do with a directive.

The Prime Directive

Here. I'll just spit out the whole class.

import { Directive, Input, OnInit } from '@angular/core';
import { MultipleClassesBaseDirective } from '../../../directives/multiple-classes-base.directive';
import { Activity } from '../models/activity';
import { ActivityOutcome } from '../models/activity-outcome';
import { ActivityService } from '../service/activity.service';

@Directive({
  selector: '[displayOutcomeStatus]'
})
export class DisplayOutcomeStatusDirective extends MultipleClassesBaseDirective implements OnInit {

  @Input('displayOutcomeStatus') activity: Activity;

  constructor(private activityService: ActivityService) {
    super();
  }

  ngOnInit() {
    this._elementClass.push('badge');
    this.addExtraClass();
  }

  private addExtraClass() {
    if (this.activity) {
      let extraClass: string = null;
      let outcome: ActivityOutcome = this.activity.outcome;
      let status: string = this.activity.status;

      if (outcome && outcome.sentiment) {
        let sentiment = outcome.sentiment;

        if (sentiment == 'POSITIVE') extraClass = 'badge-success';
        else if (sentiment == 'NEGATIVE') extraClass = 'badge-error';
        else extraClass = 'badge-info';
      } else {
        if (status == 'COMPLETED') extraClass = 'badge-success';
        else if (this.activityService.isOverdue(this.activity)) extraClass = 'badge-error';
        else if (status == 'ON_HOLD') extraClass = 'badge-warning';
      }

      if (extraClass) this._elementClass.push(extraClass);
    }
  }
} 

First, note that the class uses the @Directive decorator. You won't be shocked to learn that it identifies this class as a directive.

Next, pay attention to the selector option. That identifies the text you'll use when you want to include the directive in your HTML.

Here, it's [displayOutcomeStatus] because this directive changes the background color of the element depending on the outcome or status of the activity.

Next, note that the class extends MultipleClassesBaseDirective. That's an abstract class I created that handles a situation where you want to add more than one class to an element.

That's what's happening here. All the elements get the badge class. But they also get another class (like badge-info or badge-error) depending on the nature of the status or outcome.

So let me pause here and explain that's what this directive is doing. It's assigning a class to the element based on the value of the status or outcome object.

To associate a directive with one or more classes on an HTML element, you need use @HostBinding. You can see that in use in the base class.

Next, take a look at the @Input. For this solution, the directive text also acts as a binding object. It takes the activity object as input.

That's why you see displayOutcomeStatus in square brackets and set equal to "row":

<div *ngIf="row.outcome" [displayOutcomeStatus]="row">

That's the same kind of structure you'd see for any other @Input object in Angular. Those square brackets bind a property to an object.

In this case, it's an Activity object. It's displayed as a row in a table which is why it's called "row" in the code above.

Also note that the directive implements ngOnInit(). That's necessary because the application won't have a handle on the Activity object until the view has been initialized.

In some directive tutorials, I see developers putting stuff in the constructor. You might run into errors if you go that route.

Inside the ngOnInit() method, the code assigns the badge class to the HTML element. That's going to set the size and coloring of the lettering.

But what about the background? For that, it needs a second class.

And that's what the addExtraClass() method does. It checks the outcome/status of the activity and adds the appropriate background class.

Add It

But it's not enough to simply create a directive and leave it at that. You also need to declare it.

Do that in the module where you plan on using the directive. Like this:

@NgModule({
  declarations: [
...
    DisplayOutcomeStatusDirective,
...
  ],
  exports: [
 ...
  ],
  imports: [
...
  ]
})

Then and only then will you be able to use it.

Wrapping It Up

There you have it. Now you know why you need directives and how to create them.

Yes, I realize I went with a more advanced directive in this guide. I suppose I could have showed you how to simply change the background color of an element.

But there are plenty of other places where you'll find that kind of info. I try to focus on more practical business solutions.

So feel free to take what you've learned here to the next level.

Have fun!

Photo by Kyle Loftus from Pexels