Angular at a Glance
Angular is a TypeScript web application framework with components, templates, dependency injection, routing, forms, HTTP, reactive state, testing, and a first-party CLI.
This guide targets Angular 22.1-era standalone applications.
macOS Setup & Installation
Use a Node version manager on a development Mac so projects can target supported Node versions cleanly.
Columns: Purpose / Commands / Notes.
Purpose: Angular CLI runs on Node and npm.
nvm install 24\nnvm use 24\nnode -v\nnpm -vNotes: Node 24.15+ satisfies Angular 22. Use a project .nvmrc when a team standardizes on one Node major.
Purpose: install the ng command.
npm install -g @angular/cli\nng version\nwhich ngNotes: with nvm, routine sudo npm installs should not be necessary.
Purpose: completion, diagnostics, navigation, and refactoring.
- VS Code or WebStorm
- Angular Language Service
- Browser DevTools
Use ng completion for CLI shell completion.
Create & Run a Project
ng new creates an Angular workspace and starter application.
Create the workspace and initial application.
ng new my-app\ncd my-appBuild, watch, serve, and reload during development.
ng serve\nng serve --open\nng serve -oDefault CLI development address.
http://localhost:4200/Use another port when 4200 is occupied.
ng serve --port 4300Angular CLI Commands
The CLI executable is ng. Common aliases include g, s, b, and t.
Show framework, CLI, Node, npm, and platform versions.
ng versionInspect commands and options.
ng help
ng generate component --helpCreate a workspace and starter app.
ng new my-appDevelopment server and watcher.
ng serve
ng serve -o
ng serve --port 4300Create build output.
ng build
ng build --configuration productionScaffold a component.
ng generate component features/dashboard
ng g c features/dashboardScaffold a service.
ng generate service core/api
ng g s core/apiScaffold reusable host behavior.
ng g directive shared/autofocusScaffold a template transformation.
ng g pipe shared/initialsScaffold a route guard.
ng g guard auth/authScaffold an HTTP interceptor.
ng g interceptor core/authRun unit tests.
ng testInstall and configure a package with Angular schematics.
ng add @angular/materialShow updates or run migrations.
ng update
ng update @angular/core @angular/cliRead or set workspace configuration.
ng config
ng config cli.analytics falseSet up shell completion.
ng completionWorkspace & File Map
Common files in a current standalone Angular application.
Components & Directives
Components define UI with a template. Directives package reusable behavior on existing hosts.
Component metadata defines selector and template.
import { Component } from "@angular/core";
@Component({
selector: "app-greeting",
template: `<h2>Hello Angular</h2>`,
})
export class Greeting {}Split larger markup and styles into matching files.
@Component({
selector: "app-profile",
templateUrl: "./profile.html",
styleUrl: "./profile.css",
})
export class Profile {}Import what the template uses.
import { RouterLink } from "@angular/router";
@Component({
selector: "app-nav",
imports: [RouterLink],
template: `<a routerLink="/about">About</a>`,
})
export class Nav {}Import the child class and use its selector.
@Component({
selector: "app-page",
imports: [UserCard],
template: `<app-user-card />`,
})
export class Page {}Package reusable behavior on an existing host element.
@Directive({
selector: "[appHighlight]",
host: {
"(mouseenter)": "active.set(true)",
"(mouseleave)": "active.set(false)",
"[class.highlight]": "active()",
},
})
export class Highlight {
active = signal(false);
}Template Syntax
Angular templates are HTML plus expression, binding, control-flow, variable, and pipe syntax.
Render an expression as text.
{{ user.name }}Bind a DOM or component property.
<img [src]="photoUrl" [alt]="name">Listen for a DOM event or component output.
<button (click)="save()">Save</button>
<button (click)="select($event)">Select</button>Bind classes and styles directly.
<div [class.active]="isActive" [style.width.px]="width"></div>Built-in conditional rendering.
@if (user()) {
<p>Hello {{ user()!.name }}</p>
} @else {
<p>Please sign in.</p>
}Repeat values and track stable identity.
@for (item of items(); track item.id) {
<li>{{ item.name }}</li>
} @empty {
<li>No items</li>
}Choose among cases; no fallthrough.
@switch (role()) {
@case ("admin") { <app-admin /> }
@case ("editor") { <app-editor /> }
@default { <app-viewer /> }
}Transform values for display. Import standalone pipes used by the template.
{{ total | currency }}
{{ createdAt | date:"medium" }}Signals & Reactive State
Signals are synchronous reactive primitives. Read a signal by calling it as a function.
Create writable reactive state and read it by calling the signal.
count = signal(0);
console.log(this.count());Replace a writable signal value.
this.count.set(5);Calculate the next value from the previous value.
this.count.update(value => value + 1);Create read-only memoized derived state.
first = signal("Susan");
last = signal("Nicole");
fullName = computed(() => `${this.first()} ${this.last()}`);Use effects for appropriate imperative side effects. Prefer computed() for derived state.
logCount = effect(() => {
console.log(this.count());
});Inputs & Outputs
Inputs pass data into components. Outputs emit custom events to consumers.
Declare an optional signal-based input.
name = input("Guest");
// template reads: {{ name() }}Require the consumer to supply a value.
user = input.required<User>();
<app-user-card [user]="selectedUser()" />Declare and emit a typed custom event.
saved = output<User>();
save(user: User) {
this.saved.emit(user);
}The parent receives emitted data as $event.
<app-user-form (saved)="handleSaved($event)" />@Input(), @Output(), and EventEmitter remain supported in existing projects.
@Input() user!: User;
@Output() saved = new EventEmitter<User>();Services & Dependency Injection
Dependency injection lets classes request collaborators rather than constructing them directly.
providedIn: root is the standard global singleton pattern.
@Injectable({ providedIn: "root" })
export class UserService {}Current Angular style guidance prefers inject() for new code.
private users = inject(UserService);
private router = inject(Router);Create a service instance scoped to the component subtree.
@Component({
providers: [DraftService],
template: `...`,
})
export class Editor {}Provide typed configuration or another non-class dependency.
export const API_URL = new InjectionToken<string>("API_URL");
providers: [{ provide: API_URL, useValue: "/api" }]Router Quick Reference
Routes map URLs to components. Angular uses first-match wins.
CLI routing projects normally use src/app/app.routes.ts.
export const routes: Routes = [
{ path: "", component: Home, title: "Home" },
{ path: "users/:id", component: UserDetail },
{ path: "**", component: NotFound },
];Add the router through application providers.
export const appConfig = {
providers: [provideRouter(routes)],
};The active route component renders at the outlet.
@Component({
imports: [RouterOutlet],
template: `<router-outlet />`,
})
export class App {}Use Angular client-side navigation for internal links.
@Component({
imports: [RouterLink],
template: `<a routerLink="/users">Users</a>`,
})
export class Nav {}Use dynamic import to split non-primary route code.
{
path: "settings",
loadComponent: () => import("./settings/settings").then(m => m.Settings)
}Client guards control navigation but must not be the only authorization layer.
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
return auth.isLoggedIn() ? true : inject(Router).createUrlTree(["/login"]);
};Forms
Reactive forms remain a strong stable default. Template-driven and Signal Forms are also available.
Model-driven explicit form state.
imports: [ReactiveFormsModule]
name = new FormControl("", { nonNullable: true });
<input [formControl]="name">Group related controls.
profile = new FormGroup({
name: new FormControl("", { nonNullable: true }),
email: new FormControl("", { nonNullable: true }),
});Attach validation rules to controls.
email = new FormControl("", {
nonNullable: true,
validators: [Validators.required, Validators.email],
});Useful for smaller forms. Import FormsModule.
<input [(ngModel)]="name" name="name">Angular 21+ signal-based field trees and validation. Evaluate current stability requirements before replacing established reactive forms.
import { form, FormField } from "@angular/forms/signals";
model = signal({ email: "" });
loginForm = form(this.model);HTTP & API Calls
HttpClient is injectable by default in Angular 21+. Current Angular uses Fetch by default unless configured for XHR.
Prefer a focused API or data service.
private http = inject(HttpClient);HttpClient methods return RxJS Observables.
getUsers() {
return this.http.get<User[]>("/api/users");
}Pass query parameters through request options.
return this.http.get<User[]>("/api/users", {
params: { q: term }
});Send a typed request body.
return this.http.post<User>("/api/users", user);Match the server API contract.
this.http.put<User>("/api/users/42", user);
this.http.patch<User>("/api/users/42", changes);
this.http.delete<void>("/api/users/42");Use provideHttpClient() for interceptors or other features.
providers: [
provideHttpClient(withInterceptors([authInterceptor]))
]Clone immutable requests when adding headers.
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(AuthService).token();
return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
};RxJS Essentials
Signals fit synchronous state; RxJS remains useful for asynchronous streams and HTTP pipelines.
A lazy stream that may emit values over time.
users$ = this.http.get<User[]>("/api/users");Template subscription with Angular-managed teardown.
@for (user of users$ | async; track user.id) {
<p>{{ user.name }}</p>
}Transform emitted values.
activeUsers$ = this.users$.pipe(
map(users => users.filter(user => user.active))
);Switch to a new inner stream and cancel the previous one.
results$ = this.query$.pipe(
debounceTime(250),
distinctUntilChanged(),
switchMap(q => this.http.get<SearchResult[]>("/api/search", { params: { q } }))
);Expose the latest Observable value as a signal.
users = toSignal(this.users$, { initialValue: [] });Lifecycle & Cleanup
Use lifecycle hooks when timing matters and DestroyRef for explicit cleanup close to setup code.
Runs once after Angular initializes component inputs.
ngOnInit() {
this.load();
}Runs when component inputs change.
ngOnChanges(changes: SimpleChanges) {
console.log(changes);
}Run DOM-dependent work after the next application render.
afterNextRender(() => {
// DOM-dependent work
});Register cleanup close to setup code.
private destroyRef = inject(DestroyRef);
constructor() {
this.destroyRef.onDestroy(() => cleanup());
}Testing
New CLI projects use Vitest by default. TestBed remains the Angular-aware testing utility.
Run the workspace test target.
ng testBasic expectation syntax.
describe("math", () => {
it("adds", () => {
expect(2 + 3).toBe(5);
});
});Import a standalone component and create a fixture.
await TestBed.configureTestingModule({
imports: [Profile],
}).compileComponents();
const fixture = TestBed.createComponent(Profile);Wait for stability before asserting async rendering.
const fixture = TestBed.createComponent(Profile);
await fixture.whenStable();
const el = fixture.nativeElement as HTMLElement;
expect(el.querySelector("h1")?.textContent).toContain("Profile");Mock the HTTP backend instead of calling a real service.
TestBed.configureTestingModule({
providers: [provideHttpClient(), provideHttpClientTesting()],
});
const httpTesting = TestBed.inject(HttpTestingController);Build, Update & Deploy
Build optimized output for deployment; do not deploy the development server.
Create optimized application output.
ng build
ng build --configuration productionGenerated files normally live under dist/.
ls -la distList Angular package updates and migrations.
ng updateUpdate Angular packages together and run migrations.
ng update @angular/core @angular/cliWorks after installing a deployment integration with a deploy builder.
ng deployModern Implementation Practices
Current defaults for new Angular work plus compatibility guidance for existing projects.
Use standalone components and application providers for new work. Learn NgModules for maintenance and compatibility.
Current Angular style guidance prefers inject() for dependency injection in new code.
Use writable signals for local state and computed() for derived state.
Track stable identity such as track item.id to minimize DOM replacement.
Move reusable data and business behavior to services or plain TypeScript functions.
Lazy-load routes when it improves initial load cost; measure before over-splitting.
Use interfaces and types for API payloads, inputs, forms, and route data.
Keep Angular current, do not build templates from user input, and be cautious with APIs that bypass sanitization.
Use ng update, release notes, and the Update Guide rather than skipping many major versions.
Troubleshooting Fast Map
Check runtime versions, imports, route order, and workspace context before reinstalling everything.
Official Documentation & Downloads
Use current official documentation whenever setup or API behavior is version-sensitive.