Angular 20
Part 1: Building Your First Angular 20 Application
Web development has exploded in the last decade. Frameworks like Angular lead the charge, emphasizing performance, developer ergonomics, and modern web standards. Angular 20, released in May 2025, takes this further with stabilized signals, zoneless change detection (in developer preview), and a streamlined naming convention that drops those pesky suffixes like
.component.ts by default.
In this guide, we'll set up your first Angular 20 app using the Angular CLI. Expect cleaner file names, faster reactivity, and less boilerplate. We'll keep it practical, step-by-step, and inspired by Flavio Copes — code-first, no fluff.
Requirements
Ensure your setup includes these essentials for a smooth Angular 20 workflow:
Tool
Why You Need It
Install / Check
Node.js (LTS, e.g., 20.x or later)
Powers the CLI and runs TypeScript
https://nodejs.org
node -v
npm
Manages packages (bundled with Node)
npm -v
Git (optional)
Version control for your projects
https://git-scm.com
git --version
Pro Tip: Switch versions easily with
nvm:
nvm install --lts
nvm use --lts
Angular 20 supports modern evergreen browsers — check details at angular.dev/reference/versions#browser-support.
Install the Angular CLI
The Angular CLI is your go-to tool for scaffolding, testing, and deploying. It now aligns with Angular 20's new style guide, generating suffix-free files by default (e.g.,
app.ts instead of
app.component.ts).
Install globally:
npm install -g @angular/cli@20
Windows? Run as Administrator.
macOS/Linux? Add
sudo if prompted:
sudo npm install -g @angular/cli@20
Verify:
ng version
Output should show Angular CLI 20.x.x and related tools.
Create Your First Angular 20 App
Generate a new project named
my-blog-app:
ng new my-blog-app
Prompts you'll see:
? Would you like to share pseudonymous usage data...? (y/N) → N
? Which stylesheet format would you like to use? → CSS
? Do you want to enable Server-Side Rendering (SSR)...? (y/N) → N
Hit Enter for defaults (SSR is optional; we'll skip for simplicity).
The CLI will:
Create
my-blog-app/ folder
Install dependencies (including Angular 20 core)
Apply the new naming: No
.component suffixes!
This takes 1–3 minutes. Pro tip: Angular 20's CLI is faster thanks to optimized builds.
Legacy Mode? If you prefer old-school suffixes, add
--strict=false or configure in
angular.json later.
Run the App
Enter the project:
cd my-blog-app
Launch the dev server:
ng serve
Alias:
ng dev for quick starts.
After building (faster in v20!), open:
http://localhost:4200
Boom — the Angular welcome page loads! Live reload is on: Edit code, save, and watch updates instantly.
Project Structure (Updated for Angular 20)
Angular 20 keeps things lean. Key folders/files:
my-blog-app/
├── src/ ← Your source code hub
│ ├── app/
│ │ ├── app.ts ← Main component (no .component.ts!)
│ │ ├── app.html ← Template (no .component.html)
│ │ ├── app.css ← Styles (no .component.css)
│ │ ├── app.config.ts ← App providers
│ │ └── app.routes.ts ← Routing config
│ ├── index.html ← Entry HTML
│ ├── main.ts ← Bootstrap
│ └── styles.css ← Global CSS
├── angular.json ← Workspace config
├── package.json ← Dependencies
└── tsconfig.json ← TypeScript setup
Focus on
src/app/ — that's your playground. New naming reduces clutter:
app.ts handles logic,
app.html the markup.
Make Your First Change
Tweak the welcome message to feel the reactivity.
1. Edit the Main Component
Open
src/app/app.ts:
export class AppComponent {
title = 'My Awesome Blog'; // Updated title
}
2. Update the Template
Open
src/app/app.html and find (around line 20):
Hello, {{ title }}
Change to:
Welcome to {{ title }}! 🚀
Save. Browser auto-refreshes — see "Welcome to My Awesome Blog! 🚀"?
This uses interpolation (
{{ }}) for data binding. Angular 20's signals make this even snappier under the hood.
How It Works (Quick Peek Under the Hood)
index.html has
— Angular's mount point.
main.ts bootstraps:
bootstrapApplication(AppComponent, appConfig);
app.ts (root component) renders into the DOM.
Signals (stable in v20) handle reactivity efficiently — no more full tree diffs by default.
Useful Angular CLI Commands
Command
Alias
What It Does
ng new
n
Scaffold a new app
ng serve
dev
Dev server with HMR
ng build
b
Production build
ng generate component
g c
New component (suffix-free!)
ng test
t
Run tests
ng update
-
Upgrade to latest
Docs: angular.dev/cli
Generate with legacy suffixes:
ng g c my-comp --suffix=component.
Recommended Tools
1. VS Code + Extensions
Angular Language Service — Auto-complete in templates.
Material Icon Theme — Spot Angular files easily.
2. Angular DevTools (Chrome/Firefox)
Profile components, inspect signals: angular.dev/tools/devtools. v20 adds OnPush badges!
3. Communities
Tech Stack Nation — Beginner-friendly study group.
Angular Discord — Official hub.
What’s Next?
Your app's running — level up:
ng g c blog-post # Creates blog-post.ts/html/css (no suffixes)
Add to
app.html:
Explore signals for state:
signal('Hello'). Dive into routing or SSR next.
Angular 20's zoneless preview? Opt-in for blazing-fast apps.
Final Words
You've built an Angular 20 app: Cleaner names, stable signals, and CLI magic. It's not just a framework — it's a full ecosystem for scalable web apps.
Build iteratively. Experiment. The future's reactive.
Inspired by Flavio Copes — practical, dev-focused.
Based on Angular 20 docs and Learning Angular.
Part 2 — Structuring User Interfaces with Components
Angular applications are built from components: small, focused building blocks that each own a part of the user interface and its behavior.
In this chapter you will learn:
How an Angular 20 component is structured
How the CLI generates components with the new naming scheme
How to display and control data in templates (with the modern
@if,
@for,
@switch syntax)
How components talk to each other using inputs and outputs
How to style components and manage CSS encapsulation
How lifecycle hooks and change detection work at a high level
Where older syntax (
*ngIf,
@Input, etc.) still appears and how to read it
Anatomy of an Angular Component (Angular 20 Style)
In Angular 20, when you generate a component, the CLI now uses simpler file names:
product-list.ts (instead of
product-list.component.ts)
product-list.html
product-list.css (Ninja Squad Blog)
The idea is to reduce redundancy: the file name already tells you what this unit is.
A minimal root component looks like this:
// src/app/app.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [RouterOutlet],
standalone: true
})
export class App {
title = 'World';
}
Explanation:
selector: 'app-root' – the tag you will use in
index.html.
templateUrl /
styleUrl – point to the external HTML and CSS files.
imports: [RouterOutlet] – because Angular 16+ uses standalone components, every component explicitly imports what it needs (other components, directives, pipes).
standalone: true – tells Angular that this class stands on its own and is not declared in an NgModule.
The class is named
App (not
AppComponent) to match the new naming style.
Legacy note:
In older Angular versions, you would typically see:
File:
app.component.ts
Class:
AppComponent
No
standalone: true and no
imports array (components were declared in NgModules).
Creating a Component with the CLI
To generate a feature component in Angular 20:
ng generate component product-list
With the new naming convention, this will create:
src/app/product-list/product-list.ts
src/app/product-list/product-list.html
src/app/product-list/product-list.css
src/app/product-list/product-list.spec.ts
The TypeScript file might look like this:
// src/app/product-list/product-list.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.html',
styleUrl: './product-list.css',
standalone: true
})
export class ProductList {
}
Explanation:
The class name is
ProductList (not
ProductListComponent), consistent with the updated Angular 20 style guide.(Ninja Squad Blog)
This component does not import anything yet; we will add imports later when needed.
standalone: true makes this component directly usable in other components via the
imports array.
To use this component inside
App, you import it and add it to the
imports array:
// src/app/app.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { ProductList } from './product-list/product-list';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
standalone: true,
imports: [RouterOutlet, ProductList]
})
export class App {
title = 'World';
}
And in
app.html:
Explanation:
Importing
ProductList in
App and putting it into
imports makes Angular aware of the
app-product-list selector in this template.
The template then simply uses
, which Angular binds to the
ProductList class.
Displaying Data in the Template
Component templates can render values from the class using interpolation or property binding.
Interpolation
Hello, {{ title }}
Explanation:
{{ title }} is interpolation; Angular evaluates
title in the component instance and inserts its string value into the DOM.
Property binding
Explanation:
[innerText]="title" binds the DOM property
innerText of the
to the
title property of the component.
The square brackets indicate one-way binding from the component to the DOM.
Modern Control Flow: @if, @for, @switch
Angular 17+ introduced a new control-flow syntax that is more readable and more efficient than the older directive-based approach.
Conditional rendering with @if
Example with a product list:
// product-list.ts
import { Component } from '@angular/core';
interface Product {
id: number;
title: string;
}
@Component({
selector: 'app-product-list',
templateUrl: './product-list.html',
styleUrl: './product-list.css',
standalone: true
})
export class ProductList {
products: Product[] = [];
}
@if (products.length > 0) {
Products ({{ products.length }})
} @else {
No products found!
}
Explanation:
@if decides whether the block of HTML should exist in the DOM at all.
If
products.length > 0, Angular adds the
to the DOM; otherwise, it adds the
.
Legacy note (older Angular):
0">Products ({{ products.length }})
No products found!
*ngIf is still supported, but the new
@if syntax is the recommended style going forward.
Looping over data with @for
Let’s populate some mock products:
// product-list.ts
export class ProductList {
products: Product[] = [
{ id: 1, title: 'Keyboard' },
{ id: 2, title: 'Microphone' },
{ id: 3, title: 'Web camera' },
{ id: 4, title: 'Tablet' }
];
}
Now, use
@for in the template:
@for (product of products; track product.id) {
{{ product.title }}
} @empty {
No products found!
}
Explanation:
@for (product of products; track product.id) iterates over
products and exposes each item as
product.
track product.id tells Angular to use the
id field to keep DOM nodes stable when items change, improving performance.
@empty defines what to show when
products is an empty array.
Legacy note:
{{ product.title }}
*ngFor is the older syntax with similar behavior.
Switching templates with @switch
You can pick different content based on a value:
Explanation:
@switch (product.title) compares the title for each product.
@case defines what to render when the expression matches a specific value.
@default is rendered when no case matches.
Legacy note:
🎹🎤📦
Again,
[ngSwitch] and
*ngSwitchCase are the older equivalents.
Handling User Interaction (Event Binding)
To send information from the template back to the component, Angular uses event bindings.
Extend the
ProductList class:
// product-list.ts
export class ProductList {
products: Product[] = [
{ id: 1, title: 'Keyboard' },
{ id: 2, title: 'Microphone' },
{ id: 3, title: 'Web camera' },
{ id: 4, title: 'Tablet' }
];
selectedProduct: Product | undefined;
}
Update the template:
@for (product of products; track product.id) {
{{ product.title }}
} @empty {
No products found!
}
@if (selectedProduct) {
You selected: {{ selectedProduct.title }}
}
Explanation:
(click)="selectedProduct = product" listens for the browser’s
click event and executes the assignment in the component instance.
selectedProduct becomes the currently clicked product, and the
@if block below reacts by showing its title.
Styling Components and View Encapsulation
Angular lets you bind classes and styles dynamically.
Class binding
{{ product.title }}
Explanation:
[class.selected]="...condition..." will add or remove the
selected class based on whether the condition evaluates to
true or
false.
You can also bind an entire object:
// product-list.ts
isSelected(product: Product) {
return this.selectedProduct?.id === product.id;
}
Explanation:
[style.color] controls a single style property dynamically, based on component state.
View encapsulation
By default, Angular scopes CSS per component (Emulated mode), so styles from
product-list.css will only affect that component’s template.
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.html',
styleUrl: './product-detail.css',
standalone: true,
encapsulation: ViewEncapsulation.Emulated // default
})
export class ProductDetail {
}
If you explicitly set:
encapsulation: ViewEncapsulation.None
then styles defined in
product-detail.css can leak into other parts of the app. This can be useful for global styling, but must be used carefully.
Passing Data Between Components (Inputs and Outputs)
Real-world applications rarely keep all UI in a single component. Often, a parent component owns the data and passes a piece of it down to a child component.
Passing data down with input()
Create a detail component:
// src/app/product-detail/product-detail.ts
import { Component, input } from '@angular/core';
import type { Product } from '../product-list/product-list';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.html',
styleUrl: './product-detail.css',
standalone: true
})
export class ProductDetail {
product = input();
}
Template:
@if (product()) {
You selected:
{{ product()!.title }}
}
Explanation:
product = input() defines an input signal for this component.
In the template,
product() reads the current value of that input.
The
@if guard ensures we only render details when a product is actually provided.
Now, use
ProductDetail in
ProductList:
// product-list.ts
import { Component } from '@angular/core';
import { ProductDetail } from '../product-detail/product-detail';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.html',
styleUrl: './product-list.css',
standalone: true,
imports: [ProductDetail]
})
export class ProductList {
products: Product[] = [ /* ... */ ];
selectedProduct: Product | undefined;
}
@for (product of products; track product.id) {
{{ product.title }}
} @empty {
No products found!
}
Explanation:
[product]="selectedProduct" binds the parent’s
selectedProduct property into the child’s
product input.
Angular takes care of updating the child when the parent selection changes.
Legacy note:
Previously, you would see:
@Input() product!: Product;
instead of
product = input().
Sending events up with output()
Let the detail component notify the parent that the user wants to add the product to a cart.
In
ProductDetail:
import { Component, input, output } from '@angular/core';
import type { Product } from '../product-list/product-list';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.html',
styleUrl: './product-detail.css',
standalone: true
})
export class ProductDetail {
product = input();
added = output();
addToCart() {
if (this.product()) {
this.added.emit(this.product()!);
}
}
}
Template:
@if (product()) {
You selected:
{{ product()!.title }}
}
Explanation:
added = output() declares an output event that can carry a
Product payload.
this.added.emit(...) triggers the event.
In the parent (
ProductList):
// product-list.ts
onAdded(product: Product) {
alert(`${product.title} added to the cart!`);
}
Explanation:
(added)="onAdded($event)" listens to the child’s
added output.
$event contains the product emitted by
addToCart().
The parent can now update a cart, fire analytics, or display a message.
Legacy note:
Older Angular projects use:
@Output() added = new EventEmitter();
instead of
added = output().
Template Reference Variables and viewChild
Sometimes you need direct access to a child component instance.
Template reference variable
Detail says: {{ detail.product()!.title }}
Explanation:
#detail creates a template reference to the
ProductDetail instance.
This reference exposes the public API of
ProductDetail (here:
product()).
Querying a child in TypeScript with viewChild
You can also get the child instance from the parent class:
// product-list.ts
import { Component, AfterViewInit, viewChild } from '@angular/core';
import { ProductDetail } from '../product-detail/product-detail';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.html',
styleUrl: './product-list.css',
standalone: true,
imports: [ProductDetail]
})
export class ProductList implements AfterViewInit {
productDetail = viewChild(ProductDetail);
ngAfterViewInit(): void {
console.log('Detail product:', this.productDetail()?.product());
}
}
Explanation:
viewChild(ProductDetail) tells Angular to look for a
ProductDetail in this component’s view.
ngAfterViewInit is the hook where the child is guaranteed to be created and accessible.
Legacy note:
Previously:
@ViewChild(ProductDetail) productDetail!: ProductDetail;
Change Detection Strategy
Angular automatically refreshes views when data changes. By default, it runs change detection for the entire component tree on each relevant event.
You can optimize this with
ChangeDetectionStrategy.OnPush:
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.html',
styleUrl: './product-detail.css',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductDetail {
// ...
}
Explanation:
With
OnPush, Angular will only re-check this component when:
An input reference changes
An event handler on this component runs
An observable bound in the template emits (via async pipe), etc.
This significantly improves performance in large and complex UIs.
Lifecycle Hooks Overview
Lifecycle hooks allow you to run custom logic at specific moments in a component’s life.
Common hooks:
ngOnInit – runs after the component’s inputs are first set.
ngOnDestroy – runs right before Angular removes the component from the DOM.
ngOnChanges – runs whenever an input binding changes.
ngAfterViewInit – runs after the view and child views have been initialized.
Example:
import {
Component,
OnInit,
OnDestroy,
OnChanges,
SimpleChanges,
AfterViewInit
} from '@angular/core';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.html',
styleUrl: './product-detail.css',
standalone: true
})
export class ProductDetail
implements OnInit, OnDestroy, OnChanges, AfterViewInit {
ngOnInit(): void {
// Good place to fetch data or initialize values
console.log('ProductDetail initialized');
}
ngOnChanges(changes: SimpleChanges): void {
// React to input changes
console.log('Changes:', changes);
}
ngAfterViewInit(): void {
// Child components and view are ready
console.log('View initialized');
}
ngOnDestroy(): void {
// Cleanup: timers, subscriptions, listeners, etc.
console.log('ProductDetail destroyed');
}
}
Explanation:
Each hook gives you a predictable place to put specific kinds of logic:
ngOnInit instead of doing heavy work in the constructor.
ngOnDestroy to release resources.
ngOnChanges to react to new input values.
ngAfterViewInit to work with child components or DOM elements that weren’t available earlier.
Summary
In this chapter you have seen how, in Angular 20:
Components are generated with simpler file names like
product-list.ts,
product-list.html,
product-list.css.
Standalone components (
standalone: true) and explicit
imports have become the default way to structure an app.
The modern control-flow syntax (
@if,
@for,
@switch) replaces older structural directives in new code, while you still need to understand
*ngIf,
*ngFor and
ngSwitch for legacy templates.
Data flows into components via
input() and out via
output(), replacing
@Input and
@Output in new code.
Class and style bindings, along with view encapsulation, give you fine control over component-level CSS.
Template reference variables and
viewChild let you reach deeper into the component tree when necessary.
Change detection strategies and lifecycle hooks help you tune both performance and behavior.