Skip to content

Using attribute directives in Angular

Attribute Directives Cover

Directives are functions that enhance the current or add additional behavior to the HTML elements they are bound to.

We could divide directives into:

  • Attribute directives
  • Structural directives
  • Components

If finding components in this list feels weird do you, keep in mind that components are nothing more than HTML templates coupled together with the functionalities that directives already provide. In this article, we’ll talk about attribute directives.

Attribute directives

We use attribute directives to change the appearance of our HTML elements. One example of a built-in Angular attribute directive is the ngModel directive which creates a FormControl instance from the domain model and binds it to the form control element. With it, we are simply binding our data between both the component template and the class logic for two-way data binding. Below you can see syntactically what an attribute directive looks like. We actually have two of them, in the first one we bound the ngModel (the round and square brackets used together are colloquially known as the banana box syntax) and the appHighlight directive, which is custom-made.

<input type="text" class="name" required [(ngModel)]="name" />
<p [appHighlight]="color">Highlight me!</p>

We’ll create a different kind of attribute directive. We’ll take two inputs – the element we want to use the directive on and the text that we want to change, and we’ll have the option to change whether we want that text to be bold, italic or underlined.

So let us get started with the skeleton of our simple project. We’ll create a text input and a paragraph filled with some text. We would like to implement some logic so that we can type in our text inside the input and have it bolded/italicized/underlined in the text. We can probably start with the bold, and later add the options for italic and underline. The template and the component are then created as following:

<div class="wrapper">
  <label for="name">Enter your search:</label>
  <input type="text" id="search" [(ngModel)]="search" />
  <button type="submit">Make Bold</button>

  <p>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin dictum a erat
    in lobortis. In hac habitasse platea dictumst. Nulla sed sem condimentum,
    iaculis magna vitae, interdum sem. Duis egestas sem nibh, ut mollis tortor
    sodales vel. Vestibulum ante ipsum primis in faucibus orci luctus et
    ultrices posuere cubilia curae; Pellentesque ut sapien in eros consequat
    efficitur. Nunc imperdiet tellus arcu, vel iaculis lacus condimentum ut. Sed
    vitae sapien lacus. Phasellus sit amet volutpat lectus, sed vulputate arcu.
    Suspendisse volutpat erat sed est tincidunt, et sodales lacus semper.
    Suspendisse potenti. In sed imperdiet tellus. Maecenas elementum elit eget
    condimentum sollicitudin. In a porttitor nisl. Aliquam fringilla accumsan
    orci. Cras nec rhoncus magna. Proin hendrerit ac leo a aliquet. Maecenas
    molestie cursus rutrum. Duis dictum eleifend nisi ac tincidunt. Pellentesque
    habitant morbi tristique senectus et netus et malesuada fames ac turpis
    egestas. Donec non rutrum metus, a scelerisque sapien. Suspendisse efficitur
    mollis ante id vestibulum. Nunc lorem augue, ornare eu lorem ut, sodales
    eleifend odio. Morbi ultricies, felis nec lobortis commodo, erat nisl
    malesuada leo, nec lobortis risus ipsum nec ante. Ut lobortis, odio vel
    hendrerit commodo, lacus ipsum vestibulum metus, et sodales orci leo vitae
    ligula.
  </p>
</div>
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-search-card',
  templateUrl: './search-card.component.html',
  styleUrls: ['./search-card.component.css'],
})
export class SearchCardComponent implements OnInit {
  search: string = '';
  constructor() {}

  ngOnInit() {}
}
Attribute directives - result of the code above
Attribute directives – The result of the code above

Above we have the template and the class for the SearchCardComponent.

Let us now generate a directive, that we will call font-change. We can either create the directive manually or generate it with the following command. We also need a selector which serves the same purpose as a CSS selector. If we generated the directive we would also generate the selector – [appFontChange], meaning that the directive serves as a direct attribute of the element. Don’t forget to add the directive inside the declaration of your app.module.ts file, if you’re creating it manually.

ng generate directive font-change

Adding Inputs

Now we can add some inputs. We’ll need two in our case – one input will handle the text to update, and the other will hold the action (we’ll have just ‘bold’ for now).

import { Directive, Input } from '@angular/core';

@Directive({
  selector: '[appFontChange]',
})
export class FontChangeDirective {
  @Input() textToChange = '';
  @Input() fontAction = 'bold';
  constructor() {}
}

Next, let us plug the directive inside the template.

  <p appFontChange [textToChange]="search">
     ...
  </p>

Next, let us test whether the directive receives any input at all! For that, we’ll use the OnChanges lifecycle hook.

import { Directive, Input, OnChanges, SimpleChanges } from '@angular/core';

@Directive({
  selector: '[appFontChange]',
})
export class FontChangeDirective implements OnChanges {
  @Input() textToChange = '';
  @Input() fontAction = 'bold';
  constructor() {}

  ngOnChanges(changes: SimpleChanges) {
    console.log(changes.textToChange);
  }
}

Good progress! Now, we’re coming up to the really meaty stuff. We’ll need the template reference (of the text paragraph) so we can gain access to the element itself (in order to update the text later on). Additionally, we are checking the paragraph value for any occurrence of the text we typed, using a Regex. Below, we just print out all the matches we have.

  ngOnChanges(changes: SimpleChanges) {
    let textToChange = changes.textToChange.currentValue;
    let source = this.el.nativeElement.innerHTML;
    if (textToChange) {
      let regex = new RegExp(textToChange, 'gi');
      console.log(source.match(regex));
    }
  }

Now we need to write the replacing logic. For that, we’ll use the native .replace() method. One thing to keep in mind is that we need to store our text value into a variable so we can work our magic on that original text. Why, you might wonder? Just remove the first condition (the one that checks the firstChange) from the code block below, and you’ll find out. If we replace, the native text with the bold one, we can’t search it anymore due to it being inside the <b></b> blocks.

export class FontChangeDirective implements OnChanges {
  @Input() textToChange = '';
  @Input() fontAction = 'bold';
  source: string = '';
  constructor(private el: ElementRef) {}

  ngOnChanges(changes: SimpleChanges) {
    if (changes.textToChange.firstChange) {
      this.source = this.el.nativeElement.innerHTML;
    }

    let textToChange = changes.textToChange.currentValue;
    if (textToChange) {
      let regex = new RegExp(`(${textToChange})`, 'gi');
      this.el.nativeElement.innerHTML = this.source.replace(regex, '<b>$1</b>');
    } else {
      this.el.nativeElement.innerHTML = this.source;
    }
  }
}

And this works! If we search for any text, we’ll get the text in bold in return.

Attribute directives - getting the text bold
Attribute directives – getting the text bold.

Finishing touches

Let us also quickly add the logic or underlining and italicizing our text. We’ll add two additional buttons, next to the bold one (that currently doesn’t even serve any purpose, but it will!). Our HTML will look like this:

<div class="wrapper">
  <label for="name">Enter your search:</label>
  <input type="text" id="search" [(ngModel)]="search" />
  <button (click)="fontAction = 'b'">Make Bold</button>
  <button (click)="fontAction = 'i'">Make Italic</button>
  <button (click)="fontAction = 'u'">Make Underline</button>

  <p appFontChange [textToChange]="search" [fontAction]="fontAction">...</p>
</div>

On clicking the buttons, we’ll redeclare the fontAction variable. We are sending that same variable down to the directive. And our directive looks like this:

@Directive({
  selector: '[appFontChange]',
})
export class FontChangeDirective implements OnChanges {
  @Input() textToChange = '';
  @Input() fontAction = '';
  source: string = '';
  action: string = '';
  inputText: string = '';
  constructor(private el: ElementRef) {}

  ngOnChanges(changes: SimpleChanges) {
    if (changes?.textToChange?.firstChange) {
      this.source = this.el.nativeElement.innerHTML;
    }

    if (changes?.fontAction?.currentValue) {
      this.action = changes.fontAction.currentValue;
    }

    if (changes?.textToChange?.currentValue) {
      this.inputText = changes.textToChange.currentValue;
    }

    if (this.inputText) {
      let regex = new RegExp(`(${this.inputText})`, 'gi');
      this.el.nativeElement.innerHTML = this.source.replace(
        regex,
        `<${this.action}>$1</${this.action}>`
      );
    } else {
      this.el.nativeElement.innerHTML = this.source;
    }
  }
}

What happened here? We are checking for changes to both text and font action, and if we notice them, we will redeclare the values inside the directive-bound variables. The only other difference here is that we’ve replaced the <b></b> tags with <${this.action}>, the variable that will actually hold either ‘b’, ‘i’, or ‘u’.

The StackBlitz for this project can be found below:

1 thought on “Using attribute directives in Angular”

  1. Pingback: Using structural directives in Angular -

Comments are closed.