Angular components are subsets of directives. Directives can extend the behavior of a component by adding behavior to template elements. In this post, I'll show you how to create an attribute directive for displaying Gravatars.
What is Gravatar?
Gravatar stands for Globally Recognized Avatar. Millions of people and websites use it. If you create an account with Gravatar, you can upload your pictures and connect them with emails. When you use the same email address from Gravatar on another website, they can download your picture from Gravatar and use it on their page.
What are Directives?
Make sure to read the official docs to learn more about Angular directives. There are three types of directives:
- Components - directives with a template. The bread and butter of Angular.
- Structural directives - used for DOM manipulation, adding and removing elements. (e.g. NgFor, NgIf, etc.)
- Attribute directives - used for changing the appearance or behavior of an element, component, or another directive.
What we're going to build
We're going to create an attribute directive for displaying Gravatars. We'll have an input element where you type in an email address, and the app shows the connected picture as the avatar.
To get started, let's create an account with Gravatar.
Gravatar Account
I already have an account with Gravatar and I have connected my email addresses with some pictures. You can go to gravatar.com and create your account for free, it only takes a minute.
In the picture above you can see two emails linked with avatars. We can use these emails to get the avatars downloaded to the application.
Getting Started
Let's start by creating a new Angular project. Make sure you have Node.js, NPM, and Angular CLI installed. I'm using these versions:
- node 12.19.0
- npm 6.14.8
- Angular CLI 11.2.5
ng new ng-gravatar-directive --style=scss --routing=false
This will create a new project named ng-gravatar-directive
with SCSS styling and no routing to keep things simple. I'm also going to use Bootstrap for a basic layout and UI components:
npm install bootstrap --save
Next, import bootstrap in src/styles.scss
:
@import "~bootstrap/scss/bootstrap";
Alternatively, you could import the CSS in angular.json
:
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.scss"
]
Finally, replace the index.html
file with the following and wrap the root element in a container:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Angular Gravatar Directive</title>
</head>
<body>
<div class="container">
<main role="main">
<app-root></app-root>
</main>
</div>
</body>
</html>
The Attribute Directive
Angular directives are TypeScript classes enhanced with decorators and other metadata. You can create the directive file manually, or generate it using the CLI:
ng generate directive gravatar
This should create a file named gravatar.directive.ts
for you. Now we can add our custom behavior to DOM elements.
In the selector property, we define the directive's CSS attribute selector, [appGravatar]
. It's the brackets that make it an attribute selector. Now we can add appGravatar to the elements we want to give the behavior defined in the directive. In our example, we'll be adding new behavior to an image element:
<img appGravatar [email]="email">
We're attaching our directive to the image element by adding it as an attribute. You also notice that we're binding to a property named email
which is defined in the directive.
The Email Property
When the email address in the input field changes, we want to update the gravatar. We're using Angular's two-way data binding to send every value change in the input to the directive.
Next, let's see what we need to do to download our image and show it in the app.
The Gravatar
To download a Gravatar image we need to create a special URL using the target email address. All URLs on Gravatar are based on the use of the hashed value of an email address.
To create the correct hash string, we need to do three things:
- Trim leading and trailing whitespace from an email address
- Force all characters to lower-case
- MD5 hash the final string
The first two steps are easy:
email.trim().toLowerCase();
Now how do we create the hash? We're going to use MD5 hashing. The MD5 message-digest algorithm is a widely used hash function producing a 128-bit hash value.
I'm going to use ts-md5 but there are many other packages out there. Install this package:
npm install ts-md5 --save
And import it in the directive file:
import { Md5 } from 'ts-md5/dist/md5';
We can now use this library to generate the hash string:
const emailHash = Md5.hashStr(email.trim().toLowerCase());
The Image Request
Now that we have our hash value, we can request our image from Gravatar. The most basic request URL for Gravatars looks like this: https://www.gravatar.com/avatar/HASH
All we need to do is set this URL in the src attribute of our image element. You can pass in Gravatar options too, as query parameters. However, we don't want to manipulate the DOM element directly. We have two options in Angular:
- ElementRef - a wrapper around a native element inside a view
- Renderer2 - a base class for custom rendering, you can use this to directly manipulate the DOM
We're going to inject both of these into the directive class, to get a reference to the native element, and the renderer:
constructor(private el: ElementRef, private renderer Renderer2) { }
Now we can set the src attribute to the resulting URL in the native element. The element reference here is the element we attached our directive to, the image element.
this.renderer.setAttribute(this.el.nativeElement, 'src', '//www.gravatar.com/avatar/' + emailHash);
Here's the complete code for the directive:
import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core";
import { Md5 } from "ts-md5/dist/md5";
@Directive({
selector: "[appGravatar]"
})
export class GravatarDirective implements OnInit {
@Input() set email(value: string) {
this.updateGravatar(value);
}
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit(): void {
if (this.el) {
this.renderer.setAttribute(
this.el.nativeElement,
"src",
`//www.gravatar.com/avatar/`
);
}
}
updateGravatar(email: string): void {
if (!email || !this.el.nativeElement) {
return;
}
const emailHash = Md5.hashStr(email.trim().toLowerCase());
this.renderer.setAttribute(
this.el.nativeElement,
"src",
`//www.gravatar.com/avatar/${emailHash}?d=wavatar`
);
}
}
The email property uses the @Input
directive so that we can pass the email address to our directive. Whenever the bound value changes, we call the updateGravatar
function.
Pay attention to the ngOnInit
function. Here, we're setting the src attribute to the default gravatar image. Otherwise, the image element would be empty.
The updateGravatar
function is straightforward. If the bound email address is empty, or the native element is undefined, nothing happens. Otherwise, we generate the hash string and set the src attribute on the image element to the resulting URL.
Default Avatar
There are some possible directives to our directive. What happens when an email address has no matching Gravatar image? If we don't find the email on Gravatar, we can show a fallback image. By default, this will be the Gravatar logo. However, there are plenty of options for fallback images.
We do so by supplying the URL with the d=
or default=
parameter with our choice. In our example, I have chosen wavatar, so our URL would be: //www.gravatar.com/avatar/${emailHash}?d=wavatar
Now if we enter an email address that doesn't have a Gravatar account linked to it, we'll get a fallback image:
This was a fun example to get you started with attribute directives. Directives are a great way to increase code quality and maintainability, by encapsulating custom behaviors and DOM manipulations.
Source Code
I've put up a live demo on StackBlitz. You can play with the app on StackBlitz or get the complete code from Github. Happy coding.