In this guide, we’ll explore the heart of Angular and Vue directives, comparing their features, similarities and differences in detail.
When working with frontend technologies like Vue, and Angular, different terminologies come into play, including directives. Directives are unique constructs used in the HTML templates in Angular and Vue to inform the respective technology to do something to an element.
In this guide, we will compare between Angular and Vue directives, examining their similarities and differences. We will also explore the various directives available and how to use them in applications.
The Angular and Vue frameworks use a specific templating language that allows developers to describe the HTML content of a webpage. Directives, which are special tokens that we create or the library created for us, can be added to these templates. These directives allow us to render other components to the DOM or modify the behavior or attributes of DOM elements.
The Vue framework draws heavily on Angular’s architecture, making it a no-brainer that they share similar concepts. Angular and Vue both use directives, although their syntax and implementation differ to some extent.
This section will be divided into two parts. First, we will discuss the different types of directives available. Next, we will write code for each technology to explore its different directives.
Let’s start by cloning these repo see the code for the Angular project:
https://github.com/christiannwamba/angular-directives
Next clone the Vue project:
https://github.com/christiannwamba/vue-direectives
Directives in Angular and Vue can be categorized into three types:
The most common type of directive is component directives. When included in a template, these strings instruct the Angular or Vue library to render a component. Let’s show how they can be used by creating a simple component that renders a simple message.
Angular
Let’s create a file called welcome-message.component.ts
and update it with the following code:
import { Component } from "@angular/core";
@Component({
selector: "welcome-message",
standalone: true,
imports: [],
template: `
<div>
<h1>welcome aboard humans</h1>
</div>
`,
styles: ``,
})
export class WelcomeMessageComponent {}
This component renders a simple message on the screen and will be referenced as “welcome-message” in a template.
Let’s register and use this component in our app.component.ts
file:
import { Component } from "@angular/core";
import { WelcomeMessageComponent } from "./welcome-message/welcome-message.component";
@Component({
selector: "app-root",
standalone: true,
imports: [WelcomeMessageComponent],
template: `
<main>
<welcome-message />
</main>
`,
styles: `
`,
})
export class AppComponent {}
We registered the WelcomeMessageComponent
so whenever we include <welcome-message/>
in our template, we tell the Angular engine to render the WelcomeMessageComponent
.
Vue
Let’s create a component called WelcomeMessage.vue
and update its contents to match the following:
<template>
<div>
<h1>welcome aboard humans</h1>
</div>
</template>
Let’s now register and use this component in our root App.vue
component file like so:
<script setup lang="ts">
import WelcomeMessage from "./components/WelcomeMessage.vue"
</script>
<template>
<WelcomeMessage />
</template>
In the code above, we imported the WelcomeMessage
component. Unlike Angular, where component directive names are fixed and case-sensitive, Vue’s component directives are flexible. We can specify component directives using either camel or snake case notations. Using any of these notations will instruct the Vue library to render the WelcomeMessage
component.
Attribute directives are used in templates to allow us to modify the properties of DOM elements dynamically or to bind event handlers to the elements included in our templates.
When writing plain HTML, we can specify attributes and events on elements as shown below:
<button class="btn rounded-full" onClick="alert('hello!')">Hello<button>
Angular and Vue also provide us with custom directives specifically designed for handling most common properties and events we typically work with in our applications. These include modifying element styles, class names, working with forms, etc.
In our Angular and Vue applications, all the code we will write will be in the app.component.ts
and App.vue
files, respectively.
Angular
In Angular applications, we use the following forms of data binding:
<img src="http://test.com/cat.png" alt="happy cat" />
[
and ]
syntax can be used for dynamic attributes. These are attributes whose values will be derived from the instance variables or methods of the component. For example:@Component({
selector: "app-root",
template: ` <img [src]="imageURL" [alt]="altText" /> `,
styles: [
/*...*/
],
standalone: true,
})
export class AppComponent {
imageURL = "http://test.com/cat.png";
altText = "happy cat";
}
(
and )
syntax. For example:@Component({
selector: "app-root",
template: `
<h2>counter value is {{ count }}</h2>
<button (click)="incrementCounter()">increment counter</button>
`,
styles: [
/*...*/
],
standalone: true,
})
export class AppComponent {
count = 0;
incrementCounter() {
this.count++;
}
}
By default, Angular provides us with three main attribute directives, which are just classes. They are:
NgClass and NgStyle allow us to add dynamic class and style attributes to DOM elements. On the other hand, NgModel combines both attribute and event binding; this is used with HTML form elements. Let’s now describe each one and see how they work.
To utilize NgClass and NgStyle in components, you need to register the CommonModule
in your component, as shown below:
@Component({
selector: 'app-root',
standalone: true,
imports: [
CommonModule,
WelcomeMessageComponent,
],
NgClass: We are adding some dummy styles that add a red background and green border to our style template.
styles: `
.bg_red{
background-color: red;
}
.border_green{
border: 3px solid green;
}
`;
This directive accepts a string to represent the class name of the element.
<div ngClass="bg_red">
<h2>this h2 has a red background-color</h2>
</div>
It can also accept an array containing strings.
<div [ngClass]="['bg_red', 'border_green']">
<h2>this h2 will have a red background and a green border</h2>
</div>
It can also accept an object whose keys are the class names and their values are booleans. Only keys with truth values are rendered.
template: `
<div [ngClass]="{ bg_red: isRed, border_green: borderIsGreen }">
<h2>this h2 will have a red background-color but not a green border</h2>
</div>
`;
export class AppComponent {
isRed = true;
borderIsGreen = false;
}
You can as well just use the property binding for the class attribute.
<div [class.bg_red]="isRed">
<h2>this h2 has a red background-color 2</h2>
</div>
<!--or-->
<div [class]="isRed ? 'bg_red' : ''">
<h2>this h2 has a red background-color</h2>
</div>
NgStyle: This directive allows us to add dynamic styles to DOM elements. It accepts an Object containing some CSS. For example:
<div [ngStyle]="{ border: '2px solid red', backgroundColor: 'green' }">
<h2>this h2 will have a green background and a red border</h2>
</div>
You can also define a method that returns this object. For example:
template: `
<div [ngStyle]="getStyles()">
<h2>this h2 will have a red background</h2>
</div>
`;
export class AppComponent {
getStyles() {
return { border: "2px solid red", backgroundColor: "green" };
}
}
The ngModel Directive: This directive is used to bind form field values to component instance variables in one syntax.
To use this directive, you need to register the FormsModule
as shown below:
@Component({
selector: 'app-root',
standalone: true,
imports: [
CommonModule,
FormsModule,
WelcomeMessageComponent,
],
template `
<input type="text" [(ngModel)]="name" />
{{ name }}
`
export class AppComponent {
name=''
}
It combines the input fields change event and its value property binding in one syntax [( and ])
. The NgModel directive makes this possible. Here is an expanded version of the NgModel directive:
<input type="text" [ngModel]="name" (ngModelChange)="name = $event" />
{{ name }}
Vue
Here, the v-bind
directive is used for attribute binding like so:
<script setup lang="ts">
const imageURL = 'http://test.com/cat.png';
const altText = 'happy cat'
</script>
<template>
<img v-bind:src="imageURL" v-bind:alt="altText" />
</template>
The v-bind
directive has a shorthand which is :
. The image tag above can be written like this:
<img :src="imageURL" :alt="altText" />
The v-on
directive is also used for event bindings. Let’s add a button on our page which when clicked, alerts the message “Hello.”
<script setup lang="ts">
const saySomething = ()=>{
alert("Hello")
}
</script>
<template>
<button v-on:click="saySomething()">Click me</button>
</template>
Similarly the v-on
directive can be abbreviated as @
. This means that the click event on the button above can be rewritten as:
<button @click="saySomething()">click me please</button>
To dynamically add most common attributes, you can use the v-bind
directive. For example:
v-bind:class
: This directive enables us to dynamically assign class names to DOM elements.v-bind:style
: This directive allows us to add styles to DOM nodes dynamically.The v-bind:class Directive
It accepts a string or an array of strings as input.
<div :class="['bg_red', 'border_green']">
<h2>this h2 has a red background-color and a green border</h2>
</div>
It also accepts an object whose keys are classNames and values are booleans. Keys are applied when their values are truthy.
<script setup lang="ts">
const isRed = true;
const borderIsGreen = true;
</script>
<template>
<div :class="{ bg_red: isRed, border_green: borderIsGreen }">
<h2>this h2 might have a red background-color</h2>
</div>
</template>
It can also accept an array of objects.
<div :class="[{ bg_red: isRed},{ border_green: !borderIsGreen }]">
<h2>this h2 haves a red backound without a green border</h2>
</div>
The v-bind:style Directive
Similar to the class binding, the v:bind:style or :style can be used in the following ways:
<div :style="{border:'3px solid red'}">
<h2>this h2 has a red border</h2>
</div>
<div :style="[{border:'3px solid red'},{backgroundColor:'green'}]">
<h2>this h2 has a red border and green background</h2>
</div>
The Vue library provides us with several other directives for modifying attributes and properties on DOM elements. These are some of them:
v-show
This directive accepts a value that is converted to a boolean. If the value is falsy, the element is displayed; otherwise, it will be hidden. It does this by setting the element’s CSS display property to “none” (i.e., display: none
). For example:
<div v-show="!isRed">
<h2>this is not displayed</h2>
</div>
v-model
This directive allows us to do two-way binding. It is used on form elements to bind their value and input
event with one syntax.
<script setup lang="ts">
import { ref } from 'vue';
const name = ref('');
</script>
<template>
<input v-model="name">
{{ name }}
<!--or the longer form -->
<input :value="name" @input=" e=>name =e.target.value">
{{ name }}
</template>
Unlike attribute directives, which primarily change the properties of elements, structural directives focus more on adding new elements to our view. They are commonly used for conditional rendering or rendering lists.
One key thing to understanding how structural directives work is to understand the use of the template
HTML element. This element allows us to write markup between its opening and closing tags, which isn’t rendered by default on the page. However, we can render this template later on our page using JavaScript. For example:
<!DOCTYPE html>
<html>
<head>
<title>my page</title>
</head>
<body>
<template id="hello_temp">
<h2>hello there</h2>
</template>
<div id="outlet"></div>
<script>
const template = document.querySelector("#hello_temp");
const outlet = document.querySelector("#outlet");
const templateContent = template.content;
outlet.appendChild(templateContent.cloneNode(true));
</script>
</body>
</html>
The h2 element in the template element is not visible until it is rendered from the JavaScript code.
Angular
The following structural directives are the most commonly used in Angular:
To make use of these directives, you need to import the CommonModule
.
The ngIf directive: This directive allows us to conditionally render elements in the DOM. It accepts a condition and one or more templates. If the condition is true, the template is rendered. If the condition is false, an option template can be specified for rendering.
template: `
<div *ngIf="isHappy; then happyTemplate; else sadTemplate"></div>
<ng-template #happyTemplate>
<h1>yaay! am happy ouu la laah!</h1>
</ng-template>
<ng-template #sadTemplate>
<h1>wayoo! am sad</h1>
</ng-template>
`;
export class AppComponent {
isHappy = true;
}
We’ve defined two templates—happyTemplate
and sadTemplate
—using Angular’s template variables. Since the isHappy
boolean is set to true, happyTemplate
is rendered on the screen.
Please note the expression below is not rendered in the browser.
<div *ngIf="isHappy; then happyTemplate; else sadTemplate"></div>
We only used the div (you can use any HTML element you choose) to choose which template to render in place of the div on our page
At this point, you might wonder why we have the asterisk *
close to ngIf. This is referred to as micro syntax, which is a shorthand version. The full expression to use the ngIf directive requires the use of ng-template
with the ngIf
property binding. The above expression can be rewritten as:
<ng-template [ngIf]="!isHappy" [ngIfElse]="happyTemplate"></ng-template>
Using the wildcard *
instructs Angular to generate the full expression for us automatically.
The ngSwitch directive: This directive also allows us to do conditional rendering. It works like a regular switch statement. It’s typically used when dealing with simple equality operations. For example:
template: `
<div [ngSwitch]="club">
<p *ngSwitchCase="'liverpool'">you will never walk alone</p>
<!--or-->
<ng-template [ngSwitchCase]="'liverpool'">
<p>the Kop</p>
</ng-template>
<p *ngSwitchCase="'man city'">the cityzens</p>
<p *ngSwitchDefault>no club</p>
</div>
`;
export class AppComponent {
club = "liverpool";
}
The example above shows both the simple and expanded form of the ngSwitchCase directive. It’s important to note that unlike the regular switch statements in JavaScript, ngSwitch will render any ngSwitchCase statements that match the condition.
The ngFor directive: This directive enables us to iterate over an iterable and render a list in our templates.
template: `
<li *ngFor="let name of names; let i = index">
{{ i }} {{ name }}
</li>
`;
export class AppComponent {
names = ["Salah", "Kelvin"];
}
If you want to iterate over objects which are key value pairs you can do the following:
template: `
<li *ngFor="let item of profile | keyvalue; let i = index">
{{ i }} {{ item.value }}
</li>
`;
export class AppComponent {
profile = {
name: "john Bobo",
age: 21,
dob: "5/2/1991",
};
}
The only new change here is the keyvalue
pipe which transforms the object into an array of objects. Each with two properties key
and value
.
Angular 17 also introduced the ability to add control flow in your templates, eliminating the need for CommonModule or any imports to use them. The following replacements have been introduced:
The NgTemplateOutlet directive: This directive allows us to create embedded views in our components. For example:
<ng-template #profile let-name="name" let-userAge="age">
<p>My name is {{ name }}</p>
<p>I am {{ userAge }} years old</p>
</ng-template>
<h1>some dummy h1</h1>
<ng-container
*ngTemplateOutlet="profile; context: { name: 'Jonn', age: '56' }"></ng-container>
<ng-container
*ngTemplateOutlet="profile; context: { name: 'Bilkisu', age: '26' }"></ng-container>
The #profile
template accepts two properties: name and age. We can use the ngTemplateOutlet
to display our #profile
template on the screen while passing it the required data via the context object. This method allows us to render multiple instances of our #profile
template on our page.
In the above example, we only used ng-container with ngTemplateOutlet. This was done to avoid rendering any extra elements to the DOM. It is also possible to achieve similar results by doing this If we want a div
to wrap the rendered template.
<div
*ngTemplateOutlet="profile; context: { name: 'Bilkisu', age: '26' }"></div>
While this example is simple, the use of the ngTemplateOutlet directive is more powerful when the templates are passed from parent to child components. Child components can access these templates using the @contentChild
decorator, and passing the necessary data required to render the passed template.
Vue
Vue provides the following structural directives:
The v-if, v-else and v-else-if Directives
As shown in the snippet below, the v-else-if directive must come immediately after a v-if directive. Likewise, the v-else directive should come immediately after a v-if or v-else-if directive.
<script setup lang="ts">
const club = 'liverpool';
</script>
<template>
<div v-if="club==='liverpool'">
<p >you will never walk alone</p>
</div>
<div v-else-if="club==='man city'">
<p>the cityzens</p>
</div>
<p v-else>
no club
</p>
</template>
The v-for Directive:
This directive can be used to render lists in templates.
<script setup lang="ts">
const names = ['Salah', 'Kelvin']
</script>
<template>
<li v-for="name of names">{{name}}</li>
</template>
The v-slot directive enables content projection, which allows one component to pass its templates to another component for rendering. In other words, Component A can pass a Template B to another Component C. Despite being prepared and packaged by A, the Template B is rendered by C as though it was its own child.
Let’s update our WelcomeMessage
component like so:
<template>
<div>
<h1>welcome aboard humans!</h1>
</div>
<slot name="body">
<p>This will be the fallback</p>
</slot>
</template>
We included a slot
component, to configure this component to accept a template. Within this slot
, we’ve specified a fallback template (a paragraph in our case) which will be rendered if no template is passed.
You can have as many
slot
elements as needed.
Let’s now pass the template to the WelcomeMessage
component from our app component.
<Welcome-Message>
<template v-slot:body>
<section>
<h2>this is the projected template</h2>
</section>
</template>
</Welcome-Message>
<!--or-->
<Welcome-Message>
<template #body>
<section>
<h2>this is the projected template</h2>
</section>
</template>
</Welcome-Message>
The v-slot:body
or #body
in the code snippet above allows us to specify the name for the projected template. When we look at our application, we see the projected template displayed on the screen.
In this guide, we’ve explored the heart of Angular and Vue directives, comparing their features, similarities and differences in detail. By understanding these core concepts, you will be able to harness the power of these technologies and create dynamic and efficient web applications.
Chris Nwamba is a Senior Developer Advocate at AWS focusing on AWS Amplify. He is also a teacher with years of experience building products and communities.