Skip to main content

Angular Metadata

In Angular, metadata plays a crucial role in telling the framework how to process a class. Metadata is information that describes a class, its behavior, and how it should interact with other parts of an application. Let's dive deep into Angular metadata to understand how it works and why it's fundamental to Angular development.

Introduction to Angular Metadata

Metadata in Angular is additional information attached to classes that Angular uses to understand what the class is and how it should work within the application. This metadata is specified using decorators - special kinds of declarations that are attached to classes, methods, properties, or parameters.

Angular uses these decorators to mark TypeScript classes as Angular components, services, modules, or other Angular constructs, providing the framework with crucial information about how these elements work together.

Angular Decorators

Decorators are the primary way to add metadata to your TypeScript code in Angular. They are functions that modify JavaScript classes, properties, methods, or method parameters.

Here are the most common decorators in Angular:

  1. @Component - For defining components
  2. @NgModule - For defining modules
  3. @Injectable - For defining services
  4. @Input and @Output - For component communication
  5. @Directive - For creating directives

Let's examine each one in detail.

@Component Decorator

The @Component decorator turns a class into an Angular component. It provides Angular with metadata that tells the framework how to create and present the component in the application.

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

@Component({
selector: 'app-hello-world',
template: '<h1>Hello, World!</h1>',
styles: [`
h1 { color: blue; }
`]
})
export class HelloWorldComponent {
// Component logic goes here
}

The component metadata includes:

  • selector: A CSS selector that identifies this component in a template
  • template/templateUrl: The HTML template that defines the component's view
  • styles/styleUrls: CSS styles for the component
  • providers: Services that the component requires
  • animations: List of animations used in the component
  • and many more properties

@NgModule Decorator

The @NgModule decorator marks a class as an Angular module and provides metadata about the module.

typescript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

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

The NgModule metadata includes:

  • declarations: Components, directives, and pipes that belong to this module
  • imports: Other modules whose exported components, directives, or pipes are needed
  • exports: Components, directives, and pipes that can be used in templates of other modules
  • providers: Services that the module contributes to the global collection of services
  • bootstrap: The main component that should be bootstrapped

@Injectable Decorator

The @Injectable decorator marks a class as a service that can be injected into components and other services.

typescript
import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root' // This ensures a singleton instance is available throughout the application
})
export class DataService {
getData() {
return ['Item 1', 'Item 2', 'Item 3'];
}
}

The providedIn property specifies where the service should be provided. Setting it to 'root' makes the service available throughout the application with a single instance (singleton).

@Input and @Output Decorators

These decorators are used for component communication.

  • @Input() allows a parent component to pass data to a child component
  • @Output() allows a child component to emit events to the parent component
typescript
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
selector: 'app-child',
template: `
<div>
<p>{{ childData }}</p>
<button (click)="sendMessage()">Send Message to Parent</button>
</div>
`
})
export class ChildComponent {
@Input() childData: string;
@Output() messageEvent = new EventEmitter<string>();

sendMessage() {
this.messageEvent.emit('Hello from child component!');
}
}

In the parent component:

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

@Component({
selector: 'app-parent',
template: `
<app-child
[childData]="parentData"
(messageEvent)="receiveMessage($event)">
</app-child>
<p>{{ message }}</p>
`
})
export class ParentComponent {
parentData = 'Data from parent';
message = '';

receiveMessage(msg: string) {
this.message = msg;
}
}

@Directive Decorator

This decorator is used to create custom directives in Angular.

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

@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) { }

@HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}

@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}

private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}

Real-World Application of Metadata

Let's build a simple task management component to illustrate how metadata is used in a real-world application.

First, let's create a service to manage tasks:

typescript
import { Injectable } from '@angular/core';

interface Task {
id: number;
title: string;
completed: boolean;
}

@Injectable({
providedIn: 'root'
})
export class TaskService {
private tasks: Task[] = [
{ id: 1, title: 'Learn Angular', completed: false },
{ id: 2, title: 'Build an app', completed: false }
];

getTasks(): Task[] {
return this.tasks;
}

addTask(title: string): void {
const id = this.tasks.length ? Math.max(...this.tasks.map(t => t.id)) + 1 : 1;
this.tasks.push({ id, title, completed: false });
}

toggleComplete(id: number): void {
const task = this.tasks.find(t => t.id === id);
if (task) {
task.completed = !task.completed;
}
}
}

Now, let's create a task list component:

typescript
import { Component, OnInit } from '@angular/core';
import { TaskService } from '../task.service';

@Component({
selector: 'app-task-list',
template: `
<div class="task-container">
<h2>Task Manager</h2>

<div class="add-task">
<input [(ngModel)]="newTaskTitle" placeholder="New task..." />
<button (click)="addTask()">Add Task</button>
</div>

<div class="task-list">
<div
*ngFor="let task of tasks"
class="task-item"
[class.completed]="task.completed">
<input
type="checkbox"
[checked]="task.completed"
(change)="toggleComplete(task.id)"
/>
<span>{{ task.title }}</span>
</div>
</div>

<div class="task-stats">
<p>{{ completedCount }} of {{ tasks.length }} tasks completed</p>
</div>
</div>
`,
styles: [`
.task-container {
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
.add-task {
display: flex;
margin-bottom: 20px;
}
.add-task input {
flex-grow: 1;
padding: 8px;
margin-right: 10px;
}
.task-item {
padding: 10px;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
}
.task-item.completed span {
text-decoration: line-through;
color: #888;
}
.task-item input {
margin-right: 10px;
}
.task-stats {
margin-top: 20px;
color: #666;
}
`]
})
export class TaskListComponent implements OnInit {
tasks: any[] = [];
newTaskTitle = '';

constructor(private taskService: TaskService) { }

ngOnInit() {
this.tasks = this.taskService.getTasks();
}

addTask() {
if (this.newTaskTitle.trim()) {
this.taskService.addTask(this.newTaskTitle);
this.newTaskTitle = '';
}
}

toggleComplete(id: number) {
this.taskService.toggleComplete(id);
}

get completedCount() {
return this.tasks.filter(task => task.completed).length;
}
}

In this example:

  1. We use the @Injectable decorator to create a service for managing tasks
  2. We use the @Component decorator to create a TaskListComponent with metadata that defines its selector, template, and styles
  3. The component uses the injected TaskService to get data and perform operations

To use this component, you would need to add it to a module:

typescript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { TaskListComponent } from './task-list/task-list.component';

@NgModule({
declarations: [
AppComponent,
TaskListComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Then, you can use the task list component in your app component:

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

@Component({
selector: 'app-root',
template: `
<div class="container">
<h1>My Task Management App</h1>
<app-task-list></app-task-list>
</div>
`,
styles: [`
.container {
padding: 20px;
}
h1 {
color: #333;
text-align: center;
}
`]
})
export class AppComponent { }

Advanced Metadata Concepts

Custom Decorators

In Angular, you can create custom decorators for more specialized use cases:

typescript
// Creating a custom log decorator
function Log() {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;

descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with:`, args);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} returned:`, result);
return result;
};

return descriptor;
};
}

// Using the decorator
@Component({
selector: 'app-example',
template: '<button (click)="doSomething()">Click me</button>'
})
export class ExampleComponent {
@Log()
doSomething() {
return 'Task done!';
}
}

When doSomething() is called, it will log information about the call and return value.

View Encapsulation

The @Component decorator allows you to control how styles defined in a component are applied by setting the encapsulation property:

typescript
import { Component, ViewEncapsulation } from '@angular/core';

@Component({
selector: 'app-example',
template: `<h1>Hello World</h1>`,
styles: ['h1 { color: red; }'],
encapsulation: ViewEncapsulation.Emulated // default
})
export class ExampleComponent { }

Options for encapsulation are:

  • ViewEncapsulation.Emulated - Angular emulates shadow DOM (default)
  • ViewEncapsulation.None - No encapsulation, styles apply globally
  • ViewEncapsulation.ShadowDom - Uses browser's native shadow DOM

Change Detection Strategy

You can control how change detection works in components:

typescript
import { Component, ChangeDetectionStrategy } from '@angular/core';

@Component({
selector: 'app-optimized',
template: `<div>{{ data }}</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OptimizedComponent {
data = 'Some data';
}

Using ChangeDetectionStrategy.OnPush makes the component only check for changes when:

  • An input property changes
  • An event handler is fired
  • An observable bound to the template via the async pipe emits

Summary

Angular metadata is a fundamental concept that enables the framework to understand how your application should be structured and behave. Through decorators like @Component, @NgModule, @Injectable, @Input, and @Output, you can provide Angular with the information it needs to wire everything together correctly.

Key points to remember:

  • Metadata is provided through TypeScript decorators
  • Decorators are functions that modify classes and their members
  • The most common decorators are @Component, @NgModule, and @Injectable
  • Component metadata includes selector, template, and styles
  • Module metadata specifies declarations, imports, exports, and providers
  • Services use @Injectable to become available for dependency injection
  • @Input and @Output enable component communication

Understanding metadata is essential for effective Angular development as it forms the foundation of how components, directives, services, and modules interact with each other in your application.

Exercises

  1. Create a simple component with the @Component decorator that displays a greeting message.
  2. Create a parent and child component that communicate using @Input and @Output decorators.
  3. Create a service with the @Injectable decorator and inject it into a component.
  4. Create a custom directive with the @Directive decorator that changes the background color of an element on hover.
  5. Create a module with the @NgModule decorator that imports and exports components.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)