Angular applications use RxJS observables to manage asynchronous data streams reactively. Instead of imperative code that pulls data on demand, observables emit values over time, and components respond automatically. This reactive architecture organizes how data flows through the entire application, making state changes predictable and composable across multiple async sources.
import { Component, inject } from '@angular/core';import { UserService } from './user.service';export class PostListComponent {private userService = inject(UserService);// Observable stream of posts dataposts$ = this.userService.getPosts();constructor() {// Component subscribes and reacts to emitted valuesthis.posts$.subscribe(posts => console.log(posts));}}
Angular’s routing system provides application structure through URL-driven view composition and navigation flow control. Routes map URLs to components, enabling single-page applications to manage multiple views without full page reloads. The router handles navigation between different states and maintains browser history, so users can move forward and backward through the application and share specific URLs that load the correct view.
import { Routes } from '@angular/router';import { HomeComponent } from './home.component';import { ProfileComponent } from './profile.component';import { Router } from '@angular/router';import { inject } from '@angular/core';// Routes define URL-to-component mappingsexport const routes: Routes = [{ path: '', component: HomeComponent },{ path: 'profile/:id', component: ProfileComponent }];// Component uses router to navigateexport class NavComponent {private router = inject(Router);goToProfile(userId: string) {this.router.navigate(['/profile', userId]);}}
Angular’s form system provides user input architecture that manages data collection, validation, and state binding across the application. It offers two complementary approaches: reactive forms for explicit template-focused solutions, component-driven control and template-driven forms for simpler template-focused solutions. Both manage validation, track input state, and bind user data to the component, enabling organized input handling without scattered logic throughout the app.
import { Component } from '@angular/core';import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';export class LoginComponent {// Reactive form: explicit control in the componentloginForm = new FormGroup({email: new FormControl('', [Validators.required, Validators.email]),password: new FormControl('', Validators.required)});submit() {if (this.loginForm.valid) {console.log(this.loginForm.value);}}}
Observables represent a stream of data or events that can be subscribed (“reacted”) to by observers to implement a reactive programming paradigm. Rather than pulling data on demand, observables push values to subscribers over time. This inversion of control makes it easy to handle asynchronous operations, combine multiple data sources, and respond to changes declaratively. Observers subscribe using the subscribe() method and provide callbacks for handling emitted values, errors, and completion.
import { of } from 'rxjs';const numbers$ = of(1, 2, 3);// Observer subscribes and reacts to each emitted valuenumbers$.subscribe(value => console.log(value));
Angular uses the reactive programming library called RxJS to handle observables, observers, and subscriptions.
RxJS is used in many areas of Angular, such as component communication. It creates streams of data from asynchronous operations that components can emit, transform, and react to as the data changes over time.
import { Component, OnInit } from "@angular/core";import { Observable, of } from "rxjs";import { map } from "rxjs/operators";@Component({selector: 'app-user',template: '<p>User: {{ userName }}</p>',})export class AppUserComponent implements OnInit {userName: string = '';ngOnInit() {this.getUserData().pipe(map(user => user.name)).subscribe(name => this.userName = name);}private getUserData(): Observable<{ name: string, id: string }> {// Simulating an API callreturn of({ name: "Codey", id: "12" });}}
rxjsrxjs contains many built-in operators used to preprocess observables before subscribing to them.
At their core, operators receive a handler function to process the emitted data. These operators return a new function, which then receives an observable as an input, applies the provided handler function, and returns a new observable with the updated data.
Since raw observable data may not be ideal for an Angular component’s needs, built-in operators like map(), filter(), and reduce() can be used to process the emitted data before subscribing to the observable.
import { Component, inject, signal } from "@angular/core";import { map, filter } from "rxjs";import { fetchUserData } from "./api";@Component({selector: "app-users",...})export class AppUsersComponent {users = signal<{ id: string; name: string }[]>([])constructor() {fetchUserData().pipe(filter((user) => user.isActive),map((user) => ({ id: user.id, name: `${user.firstName} ${user.lastName}` }))).subscribe((user) => this.users.update(u => [...u, user]))}}
rxjsIn rxjs, operators are used to preprocess observable data before subscribing to it. Applying more than one operator, which is common, becomes cumbersome and confusing, so using pipes is recommended.
Pipes can contain any number of operators, and the pipe applies them to the input observable and supplies its output to the next operator.
A pipe can be created by calling the .pipe() method of an observable and passing in a comma-separated list of operators.
Finally, a pipe will output a final observable that can be subscribed to.
import { Component, OnInit } from "@angular/core"import { Subscription, map, filter } from "rxjs"import { fetchUserData } from "./api" // observable@Component({selector: "app-users",standalone: true,...})export class AppUsersComponent implements OnInit {users: {id: stringname: string}[] = []constructor() {}ngOnInit() {// create subscriptionfetchUserData().pipe( // pipefilter((user) => user.isActive), // operator 1map((user) => ({id: user.id,name: `${user.firstName} ${user.lastName}`})) // operator 2).subscribe(/* observer */)}}
Routes ArrayA Routes array contains route definitions that specify paths and their associated components. This array informs Angular which component to load when a user navigates to a particular path.
import { Routes } from '@angular/router';export const routes: Routes = [{ path: 'books', component: BooksComponent },];
Angular’s routing system allows for the hierarchical organization of routes through nested routing. This is implemented using a children array within a parent route definition. Nested routes allow multiple components to display within a parent component’s view.
{path: 'books/:id',component: BookDetailComponent,children: [{ path: 'reviews', component: ReviewsComponent },{ path: 'author', component: AuthorComponent },]}
RouterOutletThe RouterOutlet directive in Angular specifies where routed components should be displayed in the template.
In the template, it is represented by the <router-outlet> HTML element.
<!-- app.component.html --><nav><a routerLink="/home">Home</a><a routerLink="/about">About</a><a routerLink="/contact">Contact</a></nav><!-- Routed components are displayed here --><router-outlet></router-outlet>
Angular provides two ways of building and managing forms: template-driven and reactive.
Template-driven forms are created primarily in a component’s template (minimal code in the component’s class). In contrast, reactive forms are created mainly in a component’s class (minimal code in the component’s template).
Choosing which type is best depends on the following:
<!-- template-driven --><form #userForm="ngForm"><label for="name">Name:</label><input id="name" type="text" name="name" required [(ngModel)]="user.name"></form>
Reactive:
import { FormGroup, FormControl, Validators } from '@angular/forms';export class UserComponent {userForm = new FormGroup({name: new FormControl('', Validators.required)});// rest of class}
In Angular, template-driven forms are created primarily in the template using the FormsModule (@angular/forms) and HTML <form> and <input> elements.
The FormsModule provides the NgForm and NgModel directives to manage a form. The FormsModule automatically attaches an NgForm directive to <form> elements in the template, which manages the overall form state.
The NgModel directive is two-way bound to an <input> (form control) element to “connect” it to a data model in the component. This data model is updated with any changes to the form control value.
<!-- UserComponent template --><form #userForm="ngForm"><label for="name">Name:</label><!-- connected to user model in class --><input id="name" type="text" name="name" [(ngModel)]="user.name" required></form>
In Angular, reactive forms are created primarily in a component’s class using the ReactiveFormsModule (@angular/forms) and its utility classes: FormGroup and FormControl.
The overall form structure and the initial state are created in the class using FormGroup and FormControl. FormGroup contains the overall form structure and state and maintains a set of FormControl instances. A FormControl instance manages the state of a single form control. This form structure is then connected to the template using the formGroup and formControlName directives.
// others importsimport { ReactiveFormsModule, FormGroup, FormControl } from "@angular/forms";@Component({// other configurationsimports: [ReactiveFormsModule] // imports reactive forms module})export class UserComponent {// create form structure using FormGroupuserForm = new FormGroup({// create a form control with initial value using FormControlname: new FormControl("")});// rest of class}