Creating an Attribute Directive

Let's start with a simple button that moves a user to a different page.

@Component({
  selector: 'app-visit-rangle',
  template: `
    <button
      type="button"
      (click)="visitRangle()">
      Visit Rangle
    </button>
  `
})
export class VisitRangleComponent {
  visitRangle() {
    location.href = 'https://rangle.io';
  }
}

View Example

We're polite, so rather than just sending the user to a new page, we're going to ask if they're ok with that first by creating an attribute directive and attaching that to the button.

@Directive({
  selector: `[appConfirm]`
})
export class ConfirmDirective {
  @HostListener('click', ['$event'])
  confirmFirst(event: Event) {
    return window.confirm('Are you sure you want to do this?');
  }

}

View Example

Directives are created by using the @Directive decorator on a class and specifying a selector. For directives, the selector name must be camelCase and wrapped in square brackets to specify that it is an attribute binding. We're using the @HostListener decorator to listen in on events on the component or element it's attached to. In this case we're watching the click event and passing in the event details which are given by the special $event keyword. Next, we want to attach this directive to the button we created earlier.

template: `
  <button
    type="button"
    (click)="visitRangle()"
    appConfirm>
    Visit Rangle
  </button>
`

View Example

Notice, however, that the button doesn't work quite as expected. That's because while we're listening to the click event and showing a confirm dialog, the component's click handler runs before the directive's click handler and there's no communication between the two. To do this we'll need to rewrite our directive to work with the component's click handler.

@Directive({
  selector: `[appConfirm]`
})
export class ConfirmDirective {
  @Input() appConfirm = () => {};

  @HostListener('click', ['$event'])
  confirmFirst() {
    const confirmed = window.confirm('Are you sure you want to do this?');

    if(confirmed) {
      this.appConfirm();
    }
  }
}

View Example

Here, we want to specify what action needs to happen after a confirm dialog's been sent out and to do this we create an input binding just like we would on a component. We'll use our directive name for this binding and our component code changes like this:

  <button
    type="button"
    [appConfirm]="visitRangle">
    Visit Rangle
  </button>

View Example

Now our button works just as we expected. We might want to be able to customize the message of the confirm dialog however. To do this we'll use another binding.

@Directive({
  selector: `[appConfirm]`
})
export class ConfirmDirective {
  @Input() appConfirm = () => {};
  @Input() confirmMessage = 'Are you sure you want to do this?';

  @HostListener('click', ['$event'])
  confirmFirst() {
    const confirmed = window.confirm(this.confirmMessage);

    if(confirmed) {
      this.appConfirm();
    }
  }
}

View Example

Our directive gets a new input property that represents the confirm dialog message, which we pass in to window.confirm call. To take advantage of this new input property, we add another binding to our button.

<button
  type="button"
  [appConfirm]="visitRangle"
  confirmMessage="Click ok to visit Rangle.io!">
  Visit Rangle
</button>

View Example

Now we have a button with a customizable confirm message before it moves you to a new url.

Last updated