Angular course · Module 2: Components
Anatomy of an Angular component
In this lesson9
Welcome back to the Dojo training hall! In the first module you set up your first component. Now we will take a close look at it, because the whole of Angular rests on this one construct.
Imagine an application written in one huge file: a header, a list of warriors, a form and a results table, all mixed up across a thousand lines. Every fix in one place risks a breakdown somewhere completely different. It is like an army without units, where nobody knows who reports to whom.
Angular solves this problem with components. Each component is a separate warrior with its own name, equipment and duties. The whole application is an army of such warriors: the main component plays the shogun, who gives orders to the commanders, and they lead their own units.
What you will learn
In this module you will learn to:
- split a component into its three parts: the
@Componentdecorator, the class and the template - connect class data with the template through interpolation, property binding, event binding and two-way binding
- control the template with the
@if,@for,@switchand@letblocks - react to a component's birth and departure with lifecycle hooks
- pass orders from the parent with
@Inputand reports from the child with@Output
The three parts of a warrior
Every Angular component consists of three main parts. The @Component decorator tells Angular who the warrior is and how to recognize them. The component class is the warrior's body and skills: the data it remembers and the techniques it can perform. The template is everything visible on the battlefield, that is, the HTML shown to the user. These three parts always go together.
The decorator - a crest on the armor
A decorator is a function that adds metadata to a class, that is, information telling Angular how to use it. It works like a clan crest painted on armor: the warrior underneath is the same, but the crest tells everyone who they belong to and what role they play. This is what the skeleton of a component with a decorator looks like:
@Component({
// the component configuration goes here
})
export class SamuraiComponent {}A decorator always starts the same way: the at sign and the name @Component, a round bracket, and inside it a curly brace that opens the configuration object. Hence the characteristic opening @Component({, which you will write hundreds of times. At the end, the object and the bracket close in reverse order: }).
The decorator stands directly above the class it describes and applies only to that class, just as a crest adorns only its own warrior's armor.
selector - the name the warrior answers to
The first property in the configuration is selector. It is the name of your own HTML tag, which another component uses to place your warrior in its template. If you write selector: 'app-samurai' in the decorator, the parent can use it like this:
<h1>Training hall</h1>
<app-samurai></app-samurai>
<app-samurai></app-samurai>In place of each <app-samurai> tag, Angular displays the component's template. Two tags mean two separate warriors: two instances of the class, each with its own data. A change in one does not touch the other.
Notice the app- prefix and the hyphen in the name. This is a convention from the official Angular style guide: the hyphen sets your tags apart from built-in HTML tags such as <button> or <section>, and the prefix shows that the tag comes from your application.
standalone and imports - a self-reliant warrior
The second property, standalone: true, marks an independent component. Such a component declares what it needs by itself and does not have to belong to any NgModule, the clan I will tell you about in the third module. Since Angular 19 this is the default value, so new projects often leave it out. In this course we write it explicitly, so that you can see it.
A self-reliant warrior picks their own equipment. That is what the imports list is for: in it you list the other components, directives and pipes used in the template:
@Component({
selector: 'app-samurai-form',
standalone: true,
imports: [FormsModule, SamuraiComponent],
template: `...`
})
export class SamuraiFormComponent {}This form uses FormsModule, because in the next lesson you will use [(ngModel)] in it, and the SamuraiComponent component, because it places an <app-samurai> tag. Without an entry in the imports list, Angular recognizes neither of them and reports a compilation error. In older code you will often see CommonModule here, a bundle of older directives such as *ngIf and popular pipes. The new @if and @for blocks are built into Angular and need no import.
template or templateUrl
You can provide the template in two ways. A short template of a few lines goes straight into the decorator, in the template property, as a string in backticks. A longer one is better moved to a separate HTML file and pointed to with the templateUrl property. Styles work the same way: styles takes inline styles, and styleUrl points to a CSS or SCSS file.
@Component({
selector: 'app-samurai',
standalone: true,
templateUrl: './samurai.component.html',
styleUrl: './samurai.component.scss'
})
export class SamuraiComponent {}The paths are relative to the component file. In older projects you will find styleUrls with an array of files instead of styleUrl, for example styleUrls: ['./samurai.component.scss']. My advice: keep a template of up to about ten lines in template, and move a longer one to a file before the decorator turns into an unreadable scroll.
The order of the properties in the configuration object does not matter. Angular looks them up by name, so templateUrl can come before selector and the component works just the same, as long as commas separate the properties. Still, keep one order throughout the project, for example with selector at the top, because it is what you search for most often.
The class - the warrior's body and skills
Below the decorator stands the class. Its properties are the warrior's data, such as the name and level, and its methods are the warrior's behavior, that is, the techniques they can perform on command:
export class SamuraiComponent {
name = 'Musashi';
level = 1;
train(): void {
this.level++;
}
}The train() method raises the level by one. The word this points to a specific warrior, the instance whose method was called, so training one samurai does not change the level of the others. The word export lets you import the class in another file: without it the parent could not put the component on its imports list, and main.ts could not start the application's main component.
The template reaches the class's properties and methods directly, without this: {{ name }} displays the name, and (click)="train()" calls the method on a click. We will cover both syntaxes in the next lesson.
The whole warrior in one file
Time to put all the parts together. A component file always has the same order: the import at the top, the decorator with its configuration below it, and the class with its properties and methods at the end.
import { Component } from '@angular/core';
@Component({
selector: 'app-samurai',
standalone: true,
template: `
<h2>{{ name }}</h2>
<p>Level: {{ level }}</p>
<button (click)="train()">Train</button>
`,
styles: `h2 { color: darkred; }`
})
export class SamuraiComponent {
name = 'Musashi';
level = 1;
train(): void {
this.level++;
}
}The import at the top brings the Component decorator from the @angular/core package. Without it, TypeScript would not know what @Component is. The styles property takes a string or an array of strings, and its styles apply only to this component.
When the parent has SamuraiComponent on its imports list and places <app-samurai>, Angular finds the component by its selector, creates a new instance of the class and displays its template in place of the tag: the name Musashi, and below it "Level: 1". Every click on the button calls train(), and Angular refreshes the view by itself. Notice what is missing here: the class never touches the HTML. It only changes its own data, and Angular takes care of displaying it.
The training hall
Below you will find the full AppComponent from the training hall: the import, a decorator with selector, standalone, imports and styles, and a class with data and methods. Its template already uses the bindings and control flow blocks from the next lessons, so treat it as a preview of the module. The preview does not run Angular, but you can run this code locally with ng serve in your project from the first module.
Code for this lesson: src/app/app.component.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
interface Samurai {
id: number;
name: string;
clan: string;
level: number;
health: number;
}
@Component({
selector: 'app-root',
standalone: true,
imports: [FormsModule],
template: `
<div class="dojo-training">
<header>
<h1>Dojo Training Hall</h1>
<p>Module 2: Angular Components</p>
</header>
<section class="training-section">
<h2>Data Binding Demo</h2>
<!-- Interpolation -->
<div class="demo-box">
<h3 ngNonBindable>Interpolation {{ }}</h3>
<p>Name: {{ samurai.name }}</p>
<p>Clan: {{ samurai.clan }}</p>
</div>
<!-- Property Binding -->
<div class="demo-box">
<h3>Property Binding [ ]</h3>
<progress [value]="samurai.health" max="100"></progress>
<p>Health: {{ samurai.health }}%</p>
</div>
<!-- Event Binding -->
<div class="demo-box">
<h3>Event Binding ( )</h3>
<button (click)="attack()">Attack!</button>
<button (click)="heal()">Heal</button>
</div>
<!-- Two-way Binding -->
<div class="demo-box">
<h3>Two-way Binding [( )]</h3>
<input [(ngModel)]="samurai.name" placeholder="Samurai name">
<p>Current name: {{ samurai.name }}</p>
</div>
</section>
<section class="control-flow-section">
<h2>Control Flow Demo</h2>
<!-- @if -->
<div class="demo-box">
<h3>@if / @else</h3>
@if (samurai.health > 50) {
<p class="status good">Samurai in good shape!</p>
} @else if (samurai.health > 20) {
<p class="status warning">Samurai weakened</p>
} @else {
<p class="status danger">Samurai in critical condition!</p>
}
</div>
<!-- @for -->
<div class="demo-box">
<h3>@for</h3>
<ul>
@for (s of samuraiList; track s.id) {
<li [class.selected]="s.id === selectedId" (click)="selectSamurai(s.id)">
{{ s.name }} (Lvl {{ s.level }})
</li>
} @empty {
<li>No samurai</li>
}
</ul>
</div>
<!-- @switch -->
<div class="demo-box">
<h3>@switch</h3>
@switch (getRank(samurai.level)) {
@case ('master') {
<span class="badge master">Master</span>
}
@case ('advanced') {
<span class="badge advanced">Advanced</span>
}
@default {
<span class="badge beginner">Beginner</span>
}
}
</div>
</section>
</div>
`,
styles: [`
.dojo-training {
font-family: 'Segoe UI', sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 20px;
background: #1a1a2e;
color: #eee;
}
header {
text-align: center;
border-bottom: 2px solid #c9a227;
padding-bottom: 20px;
margin-bottom: 20px;
}
h1 { color: #c9a227; }
h2 { color: #c9a227; margin-top: 30px; }
h3 { color: #888; font-size: 0.9rem; margin-bottom: 10px; }
.demo-box {
background: rgba(255,255,255,0.05);
border: 1px solid #333;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
}
button {
background: #c9a227;
color: #1a1a2e;
border: none;
padding: 10px 20px;
margin: 5px;
border-radius: 4px;
cursor: pointer;
}
button:hover { opacity: 0.9; }
input {
padding: 10px;
border: 1px solid #c9a227;
border-radius: 4px;
background: #16213e;
color: #eee;
width: 200px;
}
progress {
width: 100%;
height: 20px;
}
.status { padding: 10px; border-radius: 4px; }
.status.good { background: #2d6a4f; }
.status.warning { background: #b45309; }
.status.danger { background: #991b1b; }
ul { list-style: none; padding: 0; }
li {
padding: 10px;
margin: 5px 0;
background: #16213e;
border-radius: 4px;
cursor: pointer;
}
li:hover { background: #1e3a5f; }
li.selected { border: 2px solid #c9a227; }
.badge {
padding: 5px 15px;
border-radius: 20px;
font-weight: bold;
}
.badge.master { background: #c9a227; color: #1a1a2e; }
.badge.advanced { background: #7c3aed; }
.badge.beginner { background: #4a5568; }
`]
})
export class AppComponent {
samurai: Samurai = {
id: 1,
name: 'Musashi',
clan: 'Niten Ichi-ryū',
level: 8,
health: 100
};
samuraiList: Samurai[] = [
{ id: 1, name: 'Musashi', clan: 'Niten', level: 10, health: 100 },
{ id: 2, name: 'Kojiro', clan: 'Ganryū', level: 9, health: 85 },
{ id: 3, name: 'Jubei', clan: 'Yagyu', level: 7, health: 90 }
];
selectedId = 1;
attack(): void {
this.samurai.health = Math.max(0, this.samurai.health - 15);
}
heal(): void {
this.samurai.health = Math.min(100, this.samurai.health + 20);
}
selectSamurai(id: number): void {
this.selectedId = id;
}
getRank(level: number): string {
if (level >= 9) return 'master';
if (level >= 5) return 'advanced';
return 'beginner';
}
}Remember: a component is a warrior with a crest. The decorator tells Angular who they are and how to summon them, the class carries their data and skills, and the template shows them on the battlefield.
Docs entries used in this lesson
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Which property of the @Component decorator defines the component's HTML tag name?
2. Which property of the @Component decorator marks the component as independent (not requiring an NgModule)?
3. Which three main parts does an Angular component consist of?
Hands-on tasks in the game
- Click in order
Arrange the characters to begin the component decorator on a single line: @Component({
- Code editor
Fill in the missing elements to create a SamuraiComponent with the selector 'app-samurai' that displays the warrior's name.
- Vertical ordering
Arrange the lines of the component definition from top to bottom (the order of properties inside the decorator does not matter):