Angular Components
Components are the fundamental building blocks of Angular applications. They encapsulate the data, logic, and UI elements of your application into reusable pieces. In this lesson, we'll explore Angular components in depth and learn how to create, customize, and manage them effectively.
Introduction to Angular Components
At its core, an Angular application is a tree of components with a root component (typically named AppComponent
) that contains all other components. Each component is a self-contained unit with:
- A TypeScript class that handles data and logic
- An HTML template that defines the UI
- CSS styles that define the appearance
- A component metadata (decorator) that connects these parts together
Components follow a component-based architecture pattern which helps in:
- Creating reusable, modular code
- Simplifying testing and maintenance
- Ensuring separation of concerns
Component Anatomy
Let's first understand the basic structure of an Angular component:
import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: '<h1>Hello, {{name}}!</h1>',
styles: ['h1 { color: blue; }']
})
export class HelloWorldComponent {
name: string = 'Angular';
}
Let's break down what each part means:
- Import statement: Imports the
Component
decorator from Angular's core library - @Component decorator: Provides metadata about the component
- selector: The custom HTML tag used to represent this component in templates
- template/templateUrl: The HTML markup for the component
- styles/styleUrls: The CSS styles for the component
- Component class: Contains properties and methods that are available to the template
Creating Your First Component
Let's create a simple component step by step:
Step 1: Generate a component using Angular CLI
The easiest way to create a component is using the Angular CLI:
ng generate component user-profile
# or shorter form
ng g c user-profile
This command creates four files:
user-profile.component.ts
(component class)user-profile.component.html
(template)user-profile.component.css
(styles)user-profile.component.spec.ts
(testing)
It also updates the closest module file to include the new component.
Step 2: Examine the component files
Let's look at the generated component class:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
Step 3: Add functionality to the component
Let's modify our component to display user information:
// user-profile.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent implements OnInit {
user = {
name: 'John Doe',
email: '[email protected]',
bio: 'Software developer and tech enthusiast',
joinDate: new Date(2020, 0, 15)
};
constructor() { }
ngOnInit(): void {
}
updateBio(newBio: string): void {
this.user.bio = newBio;
}
}
Step 4: Create the template
Now let's create a template to display the user data:
<!-- user-profile.component.html -->
<div class="user-profile">
<h2>User Profile</h2>
<div class="profile-card">
<h3>{{ user.name }}</h3>
<p><strong>Email:</strong> {{ user.email }}</p>
<p><strong>Member since:</strong> {{ user.joinDate | date:'longDate' }}</p>
<div class="bio-section">
<h4>Bio</h4>
<p>{{ user.bio }}</p>
<button (click)="showEditForm = !showEditForm">
{{ showEditForm ? 'Cancel' : 'Edit Bio' }}
</button>
<div *ngIf="showEditForm" class="edit-form">
<textarea #bioInput>{{ user.bio }}</textarea>
<button (click)="updateBio(bioInput.value); showEditForm = false">Save</button>
</div>
</div>
</div>
</div>
Step 5: Add some styling
/* user-profile.component.css */
.user-profile {
font-family: Arial, sans-serif;
}
.profile-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
max-width: 500px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.bio-section {
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
button {
background-color: #0066cc;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: #0055aa;
}
.edit-form {
margin-top: 10px;
}
textarea {
width: 100%;
height: 80px;
padding: 8px;
margin-bottom: 10px;
}
Step 6: Using the component
To use this component elsewhere in your application, include its selector in another component's template:
<!-- In some parent component's template -->
<app-user-profile></app-user-profile>
Component Communication
Components often need to communicate with each other. Angular provides several mechanisms for this:
1. @Input decorator - Parent to Child communication
The @Input
decorator allows a parent component to pass data to a child component.
Child component:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user-badge',
template: `
<div class="badge">
<h3>{{ userName }}</h3>
<span class="role">{{ userRole }}</span>
</div>
`,
styles: [`.badge { padding: 10px; border: 1px solid #ccc; }`]
})
export class UserBadgeComponent {
@Input() userName: string = '';
@Input() userRole: string = 'User';
}
Parent component using the child:
<app-user-badge
[userName]="'John Doe'"
[userRole]="'Administrator'">
</app-user-badge>
2. @Output decorator - Child to Parent communication
The @Output
decorator allows a child component to send events to a parent component.
Child component:
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div>
<p>Current count: {{ count }}</p>
<button (click)="increment()">Increment</button>
</div>
`
})
export class CounterComponent {
count = 0;
@Output() countChanged = new EventEmitter<number>();
increment(): void {
this.count++;
this.countChanged.emit(this.count);
}
}
Parent component:
<app-counter (countChanged)="onCountChanged($event)"></app-counter>
<p>Last emitted count: {{ lastCount }}</p>
export class ParentComponent {
lastCount = 0;
onCountChanged(count: number): void {
this.lastCount = count;
console.log('Counter updated:', count);
}
}
Component Lifecycle Hooks
Angular components have a lifecycle - they are created, updated, and eventually destroyed. Angular provides hooks into these lifecycle events:
import {
Component, OnInit, OnChanges, DoCheck, AfterViewInit,
AfterViewChecked, AfterContentInit, AfterContentChecked,
OnDestroy, SimpleChanges, Input
} from '@angular/core';
@Component({
selector: 'app-lifecycle-demo',
template: `<p>Name: {{ name }}</p>`
})
export class LifecycleDemoComponent implements
OnInit, OnChanges, DoCheck, AfterViewInit,
AfterViewChecked, AfterContentInit, AfterContentChecked,
OnDestroy {
@Input() name: string = '';
constructor() {
console.log('Constructor called');
}
ngOnChanges(changes: SimpleChanges): void {
console.log('ngOnChanges called', changes);
}
ngOnInit(): void {
console.log('ngOnInit called');
}
ngDoCheck(): void {
console.log('ngDoCheck called');
}
ngAfterContentInit(): void {
console.log('ngAfterContentInit called');
}
ngAfterContentChecked(): void {
console.log('ngAfterContentChecked called');
}
ngAfterViewInit(): void {
console.log('ngAfterViewInit called');
}
ngAfterViewChecked(): void {
console.log('ngAfterViewChecked called');
}
ngOnDestroy(): void {
console.log('ngOnDestroy called');
}
}
The most commonly used hooks are:
- ngOnInit: Called once after component initialization
- ngOnChanges: Called when an input property changes
- ngOnDestroy: Called just before Angular destroys the component
Real-World Example: Creating a Reusable Card Component
Let's create a reusable card component that can display different content with a consistent style:
// card.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.css']
})
export class CardComponent {
@Input() title: string = '';
@Input() subTitle?: string;
@Input() imageUrl?: string;
@Input() cardStyle: 'primary' | 'secondary' | 'danger' = 'primary';
}
<!-- card.component.html -->
<div class="card" [ngClass]="cardStyle">
<div class="card-header">
<h3>{{ title }}</h3>
<h4 *ngIf="subTitle">{{ subTitle }}</h4>
</div>
<div class="card-image" *ngIf="imageUrl">
<img [src]="imageUrl" alt="{{ title }}">
</div>
<div class="card-content">
<ng-content></ng-content>
</div>
</div>
/* card.component.css */
.card {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.card-header {
padding: 15px;
}
.card-header h3 {
margin: 0;
font-size: 1.4rem;
}
.card-header h4 {
margin: 5px 0 0;
font-size: 1rem;
font-weight: normal;
opacity: 0.8;
}
.card-image img {
width: 100%;
height: auto;
display: block;
}
.card-content {
padding: 15px;
}
.primary {
border-top: 3px solid #0066cc;
}
.secondary {
border-top: 3px solid #6c757d;
}
.danger {
border-top: 3px solid #dc3545;
}
Using the card component:
<!-- In some parent component -->
<app-card
title="Getting Started with Angular"
subTitle="Learn the fundamentals"
imageUrl="assets/images/angular-logo.png"
cardStyle="primary">
<p>Angular is a platform and framework for building single-page client applications using HTML and TypeScript.</p>
<button>Read more</button>
</app-card>
<app-card
title="Warning"
cardStyle="danger">
<p>Your trial period will expire in 3 days.</p>
<button>Renew now</button>
</app-card>
This example shows:
- How to create a flexible component with configurable inputs
- Using
ng-content
for content projection (inserting content from the parent) - Conditional rendering with
*ngIf
- Dynamic class binding with
[ngClass]
Advanced Component Patterns
Content Projection with Multiple Slots
Angular allows for more complex content projection using named slots:
// message-box.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-message-box',
template: `
<div class="message-box" [ngClass]="type">
<div class="header">
<ng-content select="[header]"></ng-content>
</div>
<div class="content">
<ng-content></ng-content>
</div>
<div class="footer">
<ng-content select="[footer]"></ng-content>
</div>
</div>
`,
styles: [`
.message-box {
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 15px;
}
.header {
padding: 10px;
border-bottom: 1px solid #eee;
font-weight: bold;
}
.content {
padding: 15px;
}
.footer {
padding: 10px;
border-top: 1px solid #eee;
text-align: right;
}
.info { border-left: 4px solid blue; }
.warning { border-left: 4px solid orange; }
.error { border-left: 4px solid red; }
`]
})
export class MessageBoxComponent {
@Input() type: 'info' | 'warning' | 'error' = 'info';
}
Usage:
<app-message-box type="warning">
<h3 header>Attention Required</h3>
<p>Your account needs verification before proceeding.</p>
<div footer>
<button>Verify Now</button>
<button>Remind Me Later</button>
</div>
</app-message-box>
Component View Encapsulation
Angular provides three view encapsulation strategies that control how component styles affect the DOM:
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-example',
template: `<h1>Hello</h1>`,
styles: ['h1 { color: red; }'],
encapsulation: ViewEncapsulation.Emulated // default
})
export class ExampleComponent { }
The options are:
ViewEncapsulation.Emulated
: Default - emulates scoped styles (like Shadow DOM)ViewEncapsulation.None
: No encapsulation, styles affect the whole applicationViewEncapsulation.ShadowDom
: Uses browser's native Shadow DOM
Summary
Angular components are the fundamental building blocks that make up Angular applications. They combine TypeScript code with HTML templates and CSS styles to create reusable, self-contained pieces of UI functionality.
In this lesson, we explored:
- The basic structure of an Angular component
- How to create components with the Angular CLI
- Component communication using @Input and @Output decorators
- Component lifecycle hooks
- How to create reusable, flexible components
- Advanced patterns like content projection
Understanding components is crucial for Angular development as they form the foundation of your application's architecture. By designing thoughtful, reusable components, you can create maintainable and robust applications.
Additional Resources
- Official Angular Documentation on Components
- Angular Component Interaction Guide
- Angular Content Projection Guide
Exercises
-
Basic Component: Create a profile card component that displays a person's name, photo, and bio.
-
Component Communication: Create a parent-child component pair where the parent allows setting a todo list item's text, and the child displays the item with a checkbox for completion status. When the checkbox is clicked, the child should notify the parent.
-
Dynamic Component: Create a tab container component that accepts multiple tab content components and displays only the active tab content.
-
Content Projection: Create an expandable panel component that has a header (which is always visible) and content that can be shown or hidden by clicking the header.
-
Lifecycle hooks: Create a component that logs messages to the console during each lifecycle hook to better understand the component lifecycle.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)