Skip to main content

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:

typescript
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:

bash
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:

typescript
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:

typescript
// 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:

html
<!-- 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

css
/* 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:

html
<!-- 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:

typescript
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:

html
<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:

typescript
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:

html
<app-counter (countChanged)="onCountChanged($event)"></app-counter>
<p>Last emitted count: {{ lastCount }}</p>
typescript
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:

typescript
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:

typescript
// 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';
}
html
<!-- 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>
css
/* 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:

html
<!-- 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:

  1. How to create a flexible component with configurable inputs
  2. Using ng-content for content projection (inserting content from the parent)
  3. Conditional rendering with *ngIf
  4. Dynamic class binding with [ngClass]

Advanced Component Patterns

Content Projection with Multiple Slots

Angular allows for more complex content projection using named slots:

typescript
// 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:

html
<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:

typescript
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 application
  • ViewEncapsulation.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

Exercises

  1. Basic Component: Create a profile card component that displays a person's name, photo, and bio.

  2. 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.

  3. Dynamic Component: Create a tab container component that accepts multiple tab content components and displays only the active tab content.

  4. 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.

  5. 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! :)