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';
}
}
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?');
}
}
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>
`
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();
}
}
}
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>
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();
}
}
}
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>
Now we have a button with a customizable confirm message before it moves you to a new url.