- read

How to detect clicks outside the Element in Angular 16

The Software Line 52

Photo by Markus Spiske on Unsplash

Directives in Angular are a fundamental development concept. So, let’s take a detailed look at them.

In Angular, directives are defined as classes that can add new behavior to the elements in the template or modify existing behavior.

The purpose of directives in Angular is to maneuver the DOM (Document Object Model), be it by adding new elements to DOM or removing elements and even changing the appearance of the DOM elements.

In Angular, we have three types of directives:

1. Attribute directives

Attribute directives enable Angular developers to define an attribute that, when added to an element change the appearance of the element or enhances its functionality. For example, we have built-in attribute directives such as ngStyle and ngClass.

2. Structural directives

Structural directives enable Angular developers to add, edit, and remove elements in the DOM. A good example of this is the built-in ngFor directive. Structural directives all begin with an asterisk to denote that the directive modifies the structure of the DOM.

3. Component directives

Special directives in Angular are called components, since this type of directive has a template or template URLs. In effect, it is a component directive that shows something in the DOM.

Sometimes, in Angular, we have a situation where we need to detect the outside click of an element inside our Web application. For example, we need to save note data to the database when we click outside the note field, close some dialog when we click outside the dialog container on the user interface, or remove the selection for the previously copied data from the user interface.

For all these purposes, we can create a specific attribute directive.

This directive will use only Angular core stuff like directives, output, host listener decorators, and event emitters.

import { 
Directive,
ElementRef,
EventEmitter,
HostListener,
Output
} from '@angular/core';

@Directive({
selector: '[appClickOutside]'
})
export class ClickOutsideDirective {

@Output()
appClickOutside: EventEmitter<void> = new EventEmitter();

@HostListener('document:click', ['$event'])
onDocumentClick(event: PointerEvent) {
const nativeElement: any = this.elementRef.nativeElement;
const clickedInside: boolean = nativeElement.contains(event.target);
if (!clickedInside) {
this.appClickOutside.emit();
}
}

constructor(private elementRef: ElementRef) { }

}

So, we have a class marked with a directive decorator. Notice that we have the same name for the directive selector and the directive output event name. In this way, we can use directives on the template as one line later.

The host listener is the function decorator responsible for executing a specific event handler with input parameters for a specific DOM event.

So, we are listening here for a click event on document level, and we are passing the whole event object (the pointer event object) to the document click handler.

Inside the click handler, we are checking if the event target element is not present inside the native element (the element where the directive is added), and only in that case are we using the click event emitter to emit the event without any parameters.

And now we can use the directive inside a simple component with an Angular logo image:

import { Component, OnInit } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';

@Component({
selector: 'app-click-outside-test',
template: `
<ng-container>
<img
(appClickOutside)="onOutsideImgClick()"
width="40"
alt="Angular Logo"
[src]="ngLogoImgSrc"
/>
</ng-container>
`
})
export class ClickOutsideTestComponent {

ngLogoImgSrc: SafeResourceUrl;
ngLogoBase64: string = `
data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwM
C9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEi
IGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQ
uMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLj
ItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZ
mlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJo
NDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQ
wLjl6IiAvPgogIDwvc3ZnPg==
`;

constructor(private domSanitizer: DomSanitizer) {
this.ngLogoImgSrc = this
.domSanitizer
.bypassSecurityTrustResourceUrl(this.ngLogoBase64);
}

onOutsideImgClick() {
console.log('Clicked outside img element');
}
}

Now we are rendering the test component inside the app component responsible for bootstrapping the whole application:

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: `
<div class="toolbar" role="banner">
<app-click-outside-test></app-click-outside-test>
<span>Welcome</span>
</div>
<router-outlet></router-outlet>
`,
styles: [`
:host {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 14px;
color: #333;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

.toolbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 60px;
display: flex;
align-items: center;
background-color: #1976d2;
color: white;
font-weight: 600;
}

.toolbar img {
margin: 0 16px;
}
`]
})
export class AppComponent {
title = 'ng16';
}
ng16 simple app — click outside directive test

As we can see above, the directive is working as expected.

Don’t forget to add the directive and the test component inside your app module declarations array (inside module decorator)

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ClickOutsideDirective } from './click-outside.directive';
import { ClickOutsideTestComponent } from './click-outside-test/click-outside-test.component';

@NgModule({
declarations: [
AppComponent,
ClickOutsideDirective,
ClickOutsideTestComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Conclusion

Directives are very powerful in Angular. It’s important to know when to create specific directives, so you can reuse them in your application. Don’t forget about the angular life-cycle hooks during directive creation in order to initialize the data, catch specific changes, or unsubscribe from observables. Also, deep knowledge of built-in structural and attribute directives is mandatory.

Thanks for reading.
I hope you enjoyed the article and learned something.