Angular
A quick reference for Angular — covered in my separate bootcamp (alongside Codecademy's Learn Angular course). Angular is a TypeScript-first, component-based framework with a more opinionated structure than React — components, templates, and services are explicitly "glued" together rather than composed freely.
Project Setup & CLI
The Angular CLI (installed via npm) scaffolds, serves, and builds projects, similar in role to Vite for React.
| Command | What it does |
|---|---|
| ng new my-angular-app | Scaffolds a new Angular project. |
| ng generate component user-profile | Creates a complete component (TS, HTML, CSS files) named user-profile, with all related files. |
| ng serve / npm start | Compiles and hosts the app on a local dev server, auto-refreshing on file changes. |
| ng build | Compiles and optimizes for production — output goes to the dist directory by default. |
| ng --help | Lists available CLI commands/options. |
Project Structure
Source files live under src/; the app/ folder inside it is the core of the application, hosting components, services, pipes, and directives.
| File | Purpose |
|---|---|
| app.ts / app.html / app.css | Root component's logic, template, and styles (newer streamlined naming). |
| app.routes.ts | Routing configuration. |
| app.config.ts | Application-wide configuration settings. |
Two naming conventions exist for component files:
Streamlined (newer CLI default) — files share a common base name:
app.ts, app.html, app.css
Traditional (name.type.ts) — explicit, widely used across existing
codebases, and what this course teaches for clarity:
user.component.ts → logic
user.component.html → template markup
user.component.css → component-specific styles
"user" = the specific component, "component" = explicitly indicates
the file's type/purpose — makes the relationship between files obvious
at a glance.
Components
Every component needs a TypeScript class, an HTML template, and a CSS selector, defined via the @Component decorator. This course uses standalone components exclusively (the modern approach — no NgModule boilerplate required).
import { Component } from '@angular/core';
@Component({
selector: 'app-restaurant-menu',
templateUrl: './restaurant-menu.component.html',
styleUrl: './restaurant-menu.component.css',
imports: [DishRecipeComponent, NgOptimizedImage]
})
export class RestaurantMenuComponent {
dishes = [
{ name: 'Pizza Margherita', description: '...', imageUrl: '...' }
];
}
| Metadata | Purpose |
|---|---|
| selector | The custom HTML tag used to embed this component elsewhere, e.g. 'app-restaurant-menu' → <app-restaurant-menu></app-restaurant-menu>. Matched as one whole string — the app- prefix is just CLI's default naming convention, not a special keyword. |
| templateUrl / styleUrl | Paths (relative to the component) to its HTML template and CSS file. Alternative: template/styles with inline strings. |
| standalone | Marks the component as managing its own dependencies via imports. Implicit/on by default since Angular v19 — safe to omit, but sometimes written explicitly in codebases transitioning from older Angular versions. (Since v17, CLI-generated components were already standalone by default, but standalone: true still had to be written explicitly until v19 removed that requirement.) |
| imports | Array of every component/directive this one depends on. One single imports: property — writing it twice in the same object just makes the second silently overwrite the first (a real mistake made this session); add new items into the same array, e.g. imports: [DishRecipeComponent, NgOptimizedImage]. |
Older Angular codebases may define components via NgModules instead —
that approach needs explicit module management and more boilerplate.
Standalone components (used throughout this course) are the modern,
lower-overhead replacement.
Data Binding
Connects component data (the model) to the template (the view), so changes in the data are automatically reflected in the UI with no manual DOM manipulation.
| Type | Syntax | Use |
|---|---|---|
| Interpolation | {{ '{{ value }}' }} | Inserts a text value (or expression) directly into template content. Simplest form of binding. |
| Property binding | [property]="value" | Dynamically assigns a value to an element/directive's actual DOM property — one-way, component → element only (model to view). |
| Event binding | (event)="handler($event)" | Flows the opposite direction — view to model. Responds to user interaction by calling a component method. |
// Component
export class UserProfileComponent {
userName = 'Rajesh Kumar';
user = { profileImage: 'path/to/image.jpg' };
}
<!-- Interpolation -->
<div>Hello, {{ userName }}!</div>
<!-- Property binding -->
<img [src]="user.profileImage">
<!-- Interpolation CAN also set an attribute, but mainly used for text -->
<img src="{{ user.profileImage }}">
Updates to user.profileImage in the component update the src attribute — but not the reverse; data only flows one direction, component to element.
Another common example — binding a link's href to a component property:
// Component
export class ProfileComponent {
linkedinUrl = 'https://www.linkedin.com/in/alandrylie/';
}
<a class="social-button" [href]="linkedinUrl" target="_blank">
LinkedIn
</a>
[href] works the same way as [src] above — the square brackets bind the attribute to the linkedinUrl property's value rather than treating it as a literal string. Note class and target stay as plain (unbound) HTML attributes here since they're fixed values, not driven by component data.
Another interpolation example — displaying a page heading dynamically:
// Component
export class ProfileComponent {
pageTitle = 'Alan Drylie — Full Stack Engineer';
}
<h1>{{ pageTitle }}</h1>
No square brackets needed here — {{ }} just drops the evaluated value straight into the element's text content. Use interpolation for plain text like this; reach for property binding ([ ]) instead when the target is an actual element property/attribute like src or href.
Event Binding
Flows data view → model (the reverse of interpolation/property binding). Wrap the target DOM event in parentheses, pointing to a component method that handles it.
<button (click)="toggleColor($event)">Toggle</button>
export class HomeComponent {
color: "light" | "dark" = "light";
toggleColor($event: Event) {
this.color = this.color === "light" ? "dark" : "light";
}
}
Custom events work identically to built-in DOM events — <app-user (deleted)="onUserDeleted($event)"></app-user> binds to a component's own custom event the same way (click) binds to a native one.
(eventName) vs $event — two separate things
Easy to blur together, but they answer different questions:
| Piece | What it actually does |
|---|---|
| (eventName) | Tells Angular WHICH DOM event to listen for on this element. That's the entire job of the parentheses — nothing more. |
| $event | A special variable, used only inside the quotes when calling your handler. Represents the actual native Event object that fired — same object you'd get from element.addEventListener() in plain JS. Only pass it if the handler actually needs details from the event. |
<button (click)="toggleColor()"></button> <!-- don't need event details -->
<button (click)="toggleColor($event)"></button> <!-- do need event details -->
<input (change)="onShadeChange($event)"> <!-- needs $event.target.value -->
onFavoriteShadeChange($event: Event) {
const target = $event.target as HTMLInputElement; // type cast — $event.target is
this.favoriteShade = target.value; // typed as generic EventTarget otherwise,
} // which has no .value property
(change) vs (input): change fires once the value has been altered AND committed (e.g. picking a color and closing the picker, or blurring a text field). input fires continuously as the value changes (every keystroke/live drag). Same (eventName) binding pattern either way — just a different moment of "when."
Two-Way Data Binding
Property binding (@Input) and event binding (@Output) are so often used together that Angular consolidated them into one syntax — combining [ ] and ( ) into [( )], nicknamed "banana-in-a-box." Data flows in both directions between parent and child.
<!-- Without two-way binding — written out manually -->
<account-balance [balance]="selectedAccount.balance" (balanceUpdate)="onUpdateBalance($event)" />
<!-- With two-way binding — same result, banana-in-a-box syntax -->
<account-balance-v2 [(balance)]="selectedAccount.balance" />
Making a Property Two-Way Bindable
Requires BOTH an @Input() and a matching @Output(). The @Output variable name must follow the exact pattern xChange, where x is the input's name — Angular relies on that naming convention to link the two together for the [( )] syntax to work.
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({ selector: 'account-balance-v2' })
export class AccountBalanceV2Component {
@Input() balance = 0; // must be named `balance`
@Output() balanceChange = new EventEmitter<number>(); // must be named `balanceChange`
onChangeBalance($event: Event) {
const newBalance = ($event.target as HTMLInputElement).value;
this.balance = newBalance; // update the model
this.balanceChange.emit(this.balance); // emit it back out to the parent
}
}
<input type="number" [value]="balance" (input)="onChangeBalance($event)" />
@Input() / @Output() — the manual building blocks
[( )] is really just shorthand for writing both halves separately. Worth knowing how to build them individually first:
// Child component
export class CounterComponent {
@Input() count: number = 0; // parent → child
@Output() countIncremented = new EventEmitter<number>(); // child → parent
increment() {
this.count++;
this.countIncremented.emit(this.count);
}
}
<!-- counter.component.html -->
<button (click)="increment()">Increment</button>
<!-- Parent template — property binding + event binding combined manually -->
<app-counter [count]="countValue" (countIncremented)="onCountIncremented($event)"></app-counter>
[count] sends data down (property binding), and (countIncremented) receives data flowing back up (event binding) — exactly the two halves that [(balance)] above collapses into one piece of syntax, once the xChange naming convention is followed.
Host Elements & Host Binding
A host element is the actual DOM element a component or directive is attached to — the element created when its selector tag is used. For <app-dashboard />, the host element IS <app-dashboard> itself; everything inside its template lives inside that host.
Instead of binding only inside the template, the host property on the @Component/@Directive decorator lets you bind attributes, styles, and event listeners directly onto the host element itself.
@Component({
selector: 'my-component',
host: {
'[attr.aria-label]': 'myAriaLabel', // attribute binding
'[style.background-color]': 'getStyling()', // style binding
'(click)': 'toggleActive($event)', // event listener
}
})
export class MyComponent {
isActive = false;
myAriaLabel = 'my host label';
getStyling() {
return this.isActive ? 'red' : 'green';
}
toggleActive($event: Event) {
this.isActive = !this.isActive;
}
}
Same [property] / (event) syntax as template bindings — just placed in the decorator's host object instead of directly on an HTML tag. Useful when there's no direct access to the element's own template, such as inside a custom directive.
Precedence: Host Binding vs Template Binding
| Situation | Angular picks |
|---|---|
| One value dynamic, one static | The dynamic one wins, regardless of whether it's the host or template binding. |
| Both dynamic | Host binding wins over template binding. |
| Both static | Template value wins. |
<!-- Static template vs. dynamic host → host wins (dynamic beats static) -->
<my-component aria-label="my template label" />
<!-- Result: aria-label="my host label" -->
<!-- Dynamic template vs. dynamic host → host still wins (both dynamic) -->
<my-component [attr.aria-label]="componentLabel" />
<!-- Result: aria-label is set to the host binding value -->
In general, prefer template data binding — it's clearer and keeps binding logic visible in the template. Reach for host binding when you don't have direct access to the element's own template, which is common inside custom directives (see below).
Templates & Control Flow
Built-in control-flow blocks live directly in the HTML template. Every one of them requires the @ prefix — @if, @else if, @else, @switch, @case, @default, @for, @empty — without it, Angular either throws a template parse error or renders it as literal text.
@if (a > b) {
{{ a }} is greater than {{ b }}
} @else if (b > a) {
{{ a }} is less than {{ b }}
} @else {
{{ a }} is equal to {{ b }}
}
@else / @else if must sit immediately after the previous block's closing } — same line, not on its own line below (strict adjacency, unlike a regular JS if/else).
@if (value) alone is a complete truthy/falsy check — no need for === true or !== null, same as JS/React conditionals.
@for (dish of dishes; track dish.name) {
{{ dish.name }}
} @empty {
There are no items.
}
track gives Angular a unique identifier per item for efficient re-rendering — can be a unique property (track dish.name) or the object reference itself (track dish) when each array item is already a distinct, stable object. @empty renders only when the collection is empty.
Loop variable vs. the array itself: in @for (dish of dishes; ...), dishes is the actual array property on the component; dish only exists inside the loop body — it's the current single item, name chosen arbitrarily (same idea as JS's for (const item of items)).
@switch (condition) {
@case (caseA) {
Case A.
}
@case (caseB) {
Case B.
}
@default {
Default case.
}
}
Template Variables
Use #name on an element to capture a reference to it (or a component instance) directly in the template.
<input #userInput type="text" placeholder="Enter your name" value="Codey">
<h2>{{ userInput.value }}</h2>
An IDE warning like "attribute name must be in lowercase" for something like #menuElement can be safely ignored.
Component Composition & Content Projection
A component is used inside another by importing it and referencing its selector as a tag — same underlying idea as React's "components render other components," just via the imports array instead of a JS import feeding straight into JSX.
import { MenuItemComponent } from './menu-item.component';
@Component({
selector: 'app-menu',
imports: [MenuItemComponent],
template: `<app-menu-item></app-menu-item>`,
styleUrl: './menu.component.css'
})
export class MenuComponent {
dishes = [...];
}
Content Projection
Lets a parent project content INTO a child component via <ng-content> — the child stays generic, the parent decides what actually goes inside it.
| Type | Pattern |
|---|---|
| Single-slot | One <ng-content></ng-content> placeholder — everything the parent puts between the component's opening/closing tags lands there. |
| Multi-slot | Multiple <ng-content select="..."> tags, each targeting a CSS selector — parent content gets routed to the matching slot by class name. |
<!-- Child: menu-item.component.html -->
<div class="menu-item">
<ng-content></ng-content>
</div>
<!-- Parent: menu.component.html -->
<app-menu-item>
<p>Projected Content</p>
</app-menu-item>
<!-- Resulting DOM -->
<div class="menu-item">
<p>Projected Content</p>
</div>
<!-- Multi-slot -->
<div class="menu-header">
<ng-content select=".headerContent"></ng-content>
</div>
<div class="menu-body">
<ng-content select=".bodyContent"></ng-content>
</div>
<!-- Usage -->
<app-menu-item>
<div class="headerContent">Header Content</div>
<div class="bodyContent">Body Content</div>
</app-menu-item>
Fallback Content — a real gotcha
<div class="menu-item">
<ng-content>
<p>This is the fallback, default content</p>
</ng-content>
</div>
Content placed between the opening and closing <ng-content> tags becomes fallback content — it only renders when the parent projects nothing at all. If the parent DOES project real content, the fallback is silently ignored/replaced. This is easy to trip over: writing <ng-content><p>Recipe</p></ng-content> does not mean "always show this text" — for that, keep <ng-content></ng-content> empty/self-closing instead.
Directives
A directive is reusable code that enhances the element it's applied to — manipulating layout, attributes, styling, or event handlers.
| Type | What it does | Example |
|---|---|---|
| Component | Renders a component's own template in the DOM. Every custom component you've used IS a component directive. | <app-user /> |
| Attribute | Enhances an existing element's look/behavior — applied like an HTML attribute, doesn't add/remove elements. | <img appRotate> |
| Structural | Alters DOM structure by adding/removing elements. Prefixed with * (shorthand for a longer underlying syntax). Less commonly needed directly since @if/@for/@switch cover most cases. | <img *appShow="shouldDisplayImg"> |
Built-in Attribute Directives
<!-- ngClass: conditionally add/remove a class -->
<img [ngClass]="blur ? 'overlay-blur' : ''">
<!-- ngClass: multiple classes via an object — keys are class names, values are booleans -->
<div [ngClass]="{'active': isActive, 'disabled': !isEnabled}">
Button with conditional classes
</div>
<!-- ngStyle: dynamic inline styles -->
<img [ngStyle]="{'width': size, 'height': size}">
<!-- ngStyle: a conditional value as one of the style entries -->
<div class="contact" [ngStyle]="{'background-color': contact.favorite ? favoriteShade : null}">
Both import from @angular/common and need adding to the component's imports array, same as any other dependency.
Reading the [ngStyle] ternary example above: it's three familiar pieces nested together, not new syntax — an [ngStyle] property binding, taking an object ( 'background-color' is just a string key), whose value is a plain ternary (condition ? valueIfTrue : valueIfFalse). Setting a style value to null tells Angular "don't apply this style at all," effectively removing it — different from setting it to an empty string or a literal like 'none'.
Custom Attribute Directives
Built with the @Directive() decorator (same pattern as @Component()). The selector uses square brackets to mean "applied as an attribute on an existing element," not its own tag.
import { Directive, Input, ElementRef } from '@angular/core';
@Directive({
selector: '[appRotate]',
host: {
'(click)': 'rotate()' // respond to clicks on the host element
}
})
export class RotatorDirective {
@Input() rotation = '180'; // default rotation amount, overridable by the caller
constructor(private element: ElementRef) {} // Angular injects a reference to the host DOM element
rotate() {
this.element.nativeElement.style.transform = `rotate(${this.rotation}deg)`;
}
}
<!-- Used with a fixed default -->
<img src="./my-img.jpg" appRotate>
<!-- Used with property binding to pass a dynamic rotation amount -->
<img src="./my-img.jpg" [appRotate]="rotationAmount">
ElementRef gives direct access to the host DOM element via element.nativeElement. Direct DOM manipulation in the constructor works for learning purposes, but Angular recommends using lifecycle hooks (like ngOnInit()) for this in real applications.
When the directive's input property name matches its selector exactly (e.g. an input literally named appCopy on a directive selector [appCopy]), the attribute and the value-to-pass become the same word — a common convention when a directive has one "main" value it cares about:
@Directive({ selector: '[appCopy]' })
export class AppCopyDirective {
@Input() appCopy = ''; // same name as the selector
constructor(private element: ElementRef) {
this.element.nativeElement.style.cursor = 'pointer';
}
}
<p [appCopy]="contact.phoneNumber">{{ contact.phoneNumber }}</p>
Two different jobs happening on that line: [appCopy]="..." sets the directive's input (what to copy), while {{ contact.phoneNumber }} is the separate, ordinary interpolation that actually displays the text — same value, two distinct bindings doing different things.
Pipes
A pipe transforms data for display in the template. More efficient than calling a transformation method directly in interpolation (e.g. {{ '{{ user.name.toUpperCase() }}' }}), since that would re-run on every single change-detection cycle — pipes are optimized to avoid that.
{{ dataToPipe | pipeName }}
{{ dataToPipe | pipeName: arg1 : arg2 }} <!-- extra args after a colon each -->
{{ dataToPipe | pipeOne | pipeTwo }} <!-- chaining — output of one feeds the next -->
<h1>Welcome, {{ user.name | uppercase }}</h1>
<h1>You have {{ account.balance | currency: 'USD' }}</h1>
<h1>You have {{ account.balance | number: '1.2-2' | currency: 'USD' }}</h1>
| Built-in pipe | Does |
|---|---|
| uppercase / lowercase | All caps / all lowercase. |
| titlecase | Capitalizes the first letter of every word. |
| date | Formats a date — see the format-token table below. |
| currency | Formats a number as currency, e.g. currency: 'USD'. |
| number | Formats a number, e.g. digit precision with '1.2-2'. |
Date Pipe Format Tokens — a real gotcha
{{ date | date:'fullDate' }} — note date appears TWICE meaning two different things: the first is the component's own instance variable being interpolated (could be named anything, e.g. today); the second, after the pipe, is the literal name of Angular's built-in date-formatting pipe. Pure coincidence they share a word.
For custom format strings, case sensitivity matters — this is one of the most common date-formatting bugs across every language/library using this token system (Angular, moment.js, Java, etc. all share it):
| Token | Means |
|---|---|
| d / dd | Day of month. |
| D | Day of YEAR (1–365) — rarely what you actually want. |
| M / MM | Month. |
| m / mm | MINUTES — not month. Easy trap: lowercase m means time, not date. |
| y / yy / yyyy | Year (2, or 4 digit with yyyy). |
'dd/MM/yy' // UK-style day-first → 11/08/26
'MM/dd/yy' // US-style month-first → 08/11/26
This token system isn't a "dates-only" format — it's a combined date-AND-time pattern language (e.g. 'MM/dd/yyyy HH:mm:ss'), which is why a field like minutes shares a letter with month at all: too many distinct concepts (year, month, day, hour, minute, second...) to give each its own unique letter, so case-sensitivity is used to fit more meanings into a small set of letters.
Storage/interchange format vs. display format — two separate concerns:
- Database/API storage: ISO 8601, YYYY-MM-DD (or full YYYY-MM-DDTHH:mm:ss)
— largest unit to smallest, sorts correctly as plain text, unambiguous
internationally. You rarely hand-format this yourself — a JS Date's
own .toISOString() method produces it, and most ORMs/DB drivers accept
a native Date object directly and serialize it automatically.
- UI display: locale-specific, e.g. dd/MM/yy for the UK. This is the
ONLY place the `date` pipe with a custom format string belongs — it
never touches what's actually sent to or stored in a database.
Custom Pipes
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'phonenumber' // the name used after the | in templates
})
export class PhoneNumberPipe implements PipeTransform {
transform(value: string): string {
return `(${value.slice(0, 3)}) ${value.slice(3, 6)}-${value.slice(6)}`;
}
}
<p>{{ contact.phoneNumber | phonenumber }}</p>
transform()'s first parameter is always the piped-in value; additional parameters (matching the pipe's :arg syntax) can follow. Reminder: strings already have their own .slice() method directly — no need to .split("") into a character array first just to .slice()/.join("") it back together.
Pure vs. Impure Pipes
| Type | Re-executes when... |
|---|---|
| Pure (default) | Angular detects a changed VALUE (primitives: boolean/number/string) or changed REFERENCE (objects/arrays/functions). More efficient — the default, and generally preferred. |
| Impure | Every single change-detection cycle, regardless of whether anything relevant actually changed. Needed when mutating an object/array IN PLACE (same reference, changed internals) — pure pipes miss that since the reference never changed. |
@Pipe({
name: 'myImpurePipe',
pure: false // opt into impure behavior
})
export class MyImpurePipe implements PipeTransform { /* ... */ }
Images (NgOptimizedImage)
A directive (a class attaching behavior to template elements) that automatically optimizes images for different screen sizes/conditions.
import { NgOptimizedImage } from '@angular/common';
@Component({
selector: 'app-image-display',
imports: [NgOptimizedImage],
templateUrl: './image-display.component.html',
styleUrl: './image-display.component.css'
})
export class ImageDisplayComponent {
imageUrl = 'path/to/image.jpg';
}
<!-- ngSrc replaces src — same idea, with optimizations -->
<img [ngSrc]="dish.imageUrl" width="200" height="200" alt="{{ dish.name }}">
<!-- placeholder shows a temporary image while the main one loads -->
<img [ngSrc]="product.imageUrl" width="400" height="200" placeholder alt="Product Image">
NgOptimizedImage requires either both width AND height, or the fill attribute, to be set. placeholder can also be a URL to a low-res or base64-encoded image for a smoother loading transition.
width/height are PLAIN HTML ATTRIBUTES here, not CSS:
- Accept only a unitless number, interpreted as pixels automatically.
- width="200" is correct; width="200px" is invalid for an HTML attribute.
- rem/%/em are NOT supported this way at all — for those, use actual
CSS instead: a stylesheet rule, an inline `style` attribute, or
Angular's style binding, e.g. [style.width.rem]="12".
The "Glue" Model: HTML → Component → Service
A mental model from my bootcamp instructor (AR) for how the pieces connect: the HTML template is glued to its component, and the component is glued to whatever services it needs via inject().
<!-- HTML: glued to the component via event binding -->
<button (click)="dog()">Click</button>
// Component: glued to services via inject()
export class SomeComponent {
ds = inject(DogService);
n = inject(NarutoService);
dog() {
this.ds.banana();
this.n.rasengan();
}
}
Two valid shapes for how services connect — both were taught, not either/or:
| Shape | Description |
|---|---|
| Parallel | A component can glue to MULTIPLE independent services side by side — the component is the hub, each service its own separate glue-point (as in the example above: DogService and NarutoService are unrelated to each other). |
| Chained | A service can itself glue to another service, using the exact same inject() pattern one level down — no syntax difference between a component injecting a service and a service injecting another service. |
// Chained example: HTML → Component → CatService → DbService
// Component
export class SomeComponent {
catService = inject(CatService);
cat() {
this.catService.getCat();
}
}
// CatService — itself glues to another service
export class CatService {
dbService = inject(DbService);
getCat() {
this.dbService.db();
}
}
// DbService
export class DbService {
db() { ... }
}
Same mechanism at every link in the chain: whichever class you're in (component or service), inject whatever you depend on as a class property, then call through it with this.propertyName.methodName().