
angular-development
Comprehensive Angular framework development covering components, directives, services, dependency in
Angular Development Skill
A comprehensive skill for building modern Angular applications with standalone components, signals, reactive forms, routing, and RxJS integration.
Overview
This skill provides deep expertise in Angular framework development, covering everything from basic component creation to advanced patterns like lazy loading, state management with signals, and reactive programming with RxJS. Based on official Angular documentation (Context7 Trust Score: 8.9), it incorporates the latest best practices and modern patterns.
What This Skill Covers
Core Angular Concepts
- Components: Standalone components, lifecycle hooks, component communication
- Services: Dependency injection, providedIn configuration, service patterns
- Directives: Structural and attribute directives, custom directive creation
- Pipes: Built-in pipes, custom pipe implementation, pure vs impure pipes
- Modules: Migration from NgModules to standalone components
Modern Angular Features
- Signals: Reactive state management with signals, computed values, effects
- Control Flow: New @if, @for, @switch syntax replacing *ngIf, *ngFor, *ngSwitch
- inject() Function: Modern dependency injection replacing constructor injection
- Standalone Components: Module-less architecture for better tree-shaking
- Signal-based Inputs/Outputs: Modern component communication patterns
Routing and Navigation
- Lazy Loading: Route-level code splitting for performance
- Guards: Functional and class-based route guards
- Resolvers: Pre-fetching data before route activation
- Route Parameters: Accessing and reacting to route params
- Child Routes: Nested routing patterns
Forms
- Reactive Forms: FormBuilder, FormGroup, FormControl, FormArray
- Validation: Built-in validators, custom validators, async validators
- Dynamic Forms: Programmatic form generation
- Form State: Tracking touched, dirty, valid states
- Custom Form Controls: ControlValueAccessor implementation
RxJS Integration
- Observables: Creating and subscribing to observables
- Operators: map, filter, switchMap, combineLatest, debounceTime, etc.
- Subjects: BehaviorSubject, Subject, ReplaySubject patterns
- Error Handling: retry, catchError operators
- Subscription Management: takeUntilDestroyed, async pipe
HTTP and Data
- HttpClient: GET, POST, PUT, PATCH, DELETE operations
- Interceptors: Request/response interception
- Error Handling: Global and local error handling
- Type Safety: Typed HTTP responses
- Caching: Response caching strategies
State Management
- Signals: Modern reactive state with fine-grained updates
- Services: Service-based state management
- BehaviorSubject: Observable state patterns (legacy)
- Computed Values: Derived state with signals
- Effects: Side effects triggered by signal changes
Performance Optimization
- Lazy Loading: Route and module lazy loading
- OnPush Change Detection: Optimized change detection strategy
- TrackBy: Efficient list rendering
- Virtual Scrolling: CDK virtual scroll for large lists
- Memoization: Computed signals for expensive calculations
Testing
- Component Tests: TestBed, ComponentFixture, async testing
- Service Tests: HttpClientTestingModule, mock services
- Dependency Injection: Testing with DI
- Spy Objects: Jasmine spies and mocks
- E2E Testing: End-to-end testing patterns
When to Use This Skill
Use this skill when you need to:
- Build a new Angular application from scratch
- Migrate an existing Angular app to modern patterns (standalone, signals, inject())
- Implement complex forms with validation
- Set up routing with lazy loading and guards
- Integrate RxJS for reactive programming
- Optimize Angular application performance
- Implement state management patterns
- Create reusable components and services
- Set up HTTP client with interceptors
- Write tests for Angular components and services
- Build enterprise-scale Angular applications
- Implement modern Angular best practices
Quick Start Examples
Creating a Standalone Component
import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-welcome',
standalone: true,
imports: [CommonModule],
template: `
<div class="welcome">
<h1>{{ title() }}</h1>
<p>{{ message() }}</p>
<button (click)="updateMessage()">Update</button>
</div>
`
})
export class WelcomeComponent {
title = signal('Welcome');
message = signal('Hello, Angular!');
updateMessage() {
this.message.set('Message updated!');
}
}
Creating a Service with inject()
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
private http = inject(HttpClient);
getData(): Observable<any[]> {
return this.http.get<any[]>('/api/data');
}
}
Setting Up Lazy Loading Routes
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'home',
loadComponent: () => import('./home/home.component')
.then(m => m.HomeComponent)
},
{
path: 'dashboard',
loadComponent: () => import('./dashboard/dashboard.component')
.then(m => m.DashboardComponent)
}
];
Building a Reactive Form
import { Component, inject } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-contact-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input formControlName="name" placeholder="Name">
<input formControlName="email" placeholder="Email">
<button type="submit" [disabled]="form.invalid">Submit</button>
</form>
`
})
export class ContactFormComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
});
onSubmit() {
if (this.form.valid) {
console.log(this.form.value);
}
}
}
Key Patterns and Best Practices
1. Use Standalone Components
Standalone components simplify the architecture and improve tree-shaking:
@Component({
selector: 'app-my-component',
standalone: true,
imports: [CommonModule, FormsModule],
template: `...`
})
export class MyComponent {}
2. Leverage the inject() Function
The inject() function provides cleaner dependency injection:
export class MyService {
private http = inject(HttpClient);
private router = inject(Router);
}
3. Use Signals for State Management
Signals provide fine-grained reactivity with automatic dependency tracking:
export class CounterService {
count = signal(0);
doubleCount = computed(() => this.count() * 2);
increment() {
this.count.update(n => n + 1);
}
}
4. Implement Lazy Loading
Lazy load routes for better performance:
{
path: 'admin',
loadComponent: () => import('./admin/admin.component')
.then(m => m.AdminComponent)
}
5. Use Reactive Forms
Reactive forms provide better type safety and testability:
form = this.fb.group({
username: ['', [Validators.required, Validators.minLength(3)]],
password: ['', [Validators.required, Validators.minLength(8)]]
});
6. Handle Subscriptions Properly
Always clean up subscriptions to prevent memory leaks:
// Use async pipe (automatically unsubscribes)
data$ = this.service.getData();
// Or use takeUntilDestroyed
ngOnInit() {
this.service.getData()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(data => this.data = data);
}
7. Use OnPush Change Detection
Optimize performance with OnPush strategy:
@Component({
selector: 'app-optimized',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `...`
})
export class OptimizedComponent {}
8. Type Everything
Leverage TypeScript's type system:
interface User {
id: number;
name: string;
email: string;
}
getUser(id: number): Observable<User> {
return this.http.get<User>(`/api/users/${id}`);
}
9. Use TrackBy Functions
Improve rendering performance with trackBy:
@for (item of items; track item.id) {
<div>{{ item.name }}</div>
}
10. Implement Proper Error Handling
Always handle errors in HTTP requests:
getData(): Observable<Data[]> {
return this.http.get<Data[]>('/api/data').pipe(
retry(3),
catchError(error => {
console.error('Error:', error);
return of([]);
})
);
}
Modern Angular Architecture
Application Bootstrap
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
import { authInterceptor } from './interceptors/auth.interceptor';
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes),
provideHttpClient(
withInterceptors([authInterceptor])
)
]
});
Feature-Based Organization
src/app/
├── core/
│ ├── services/
│ ├── guards/
│ ├── interceptors/
│ └── models/
├── features/
│ ├── users/
│ │ ├── components/
│ │ ├── services/
│ │ └── models/
│ └── products/
│ ├── components/
│ ├── services/
│ └── models/
└── shared/
├── components/
├── directives/
└── pipes/
Smart vs Presentational Components
Presentational (Dumb) Component:
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<div class="card">
<h3>{{ user().name }}</h3>
<button (click)="edit.emit(user())">Edit</button>
</div>
`
})
export class UserCardComponent {
user = input.required<User>();
edit = output<User>();
}
Smart (Container) Component:
@Component({
selector: 'app-user-list',
standalone: true,
imports: [UserCardComponent],
template: `
@for (user of users$ | async; track user.id) {
<app-user-card
[user]="user"
(edit)="handleEdit($event)"
/>
}
`
})
export class UserListComponent {
private userService = inject(UserService);
users$ = this.userService.getUsers();
handleEdit(user: User) {
this.userService.updateUser(user.id, user).subscribe();
}
}
Migration Strategies
From NgModules to Standalone
Before:
@NgModule({
declarations: [MyComponent],
imports: [CommonModule],
exports: [MyComponent]
})
export class MyModule {}
After:
@Component({
selector: 'app-my-component',
standalone: true,
imports: [CommonModule],
template: `...`
})
export class MyComponent {}
From Constructor Injection to inject()
Before:
constructor(
private http: HttpClient,
private router: Router
) {}
After:
private http = inject(HttpClient);
private router = inject(Router);
From *ngIf to @if
Before:
<div *ngIf="isVisible">Content</div>
<div *ngIf="isVisible; else elseBlock">Content</div>
<ng-template #elseBlock>Else content</ng-template>
After:
@if (isVisible) {
<div>Content</div>
}
@if (isVisible) {
<div>Content</div>
} @else {
<div>Else content</div>
}
From *ngFor to @for
Before:
<div *ngFor="let item of items; trackBy: trackById">
{{ item.name }}
</div>
After:
@for (item of items; track item.id) {
<div>{{ item.name }}</div>
}
From BehaviorSubject to Signals
Before:
private countSubject = new BehaviorSubject(0);
count$ = this.countSubject.asObservable();
increment() {
this.countSubject.next(this.countSubject.value + 1);
}
After:
count = signal(0);
increment() {
this.count.update(n => n + 1);
}
Common Use Cases
Authentication Flow
@Injectable({ providedIn: 'root' })
export class AuthService {
private http = inject(HttpClient);
private router = inject(Router);
isAuthenticated = signal(false);
currentUser = signal<User | null>(null);
login(credentials: { email: string; password: string }) {
return this.http.post<{ user: User; token: string }>('/api/login', credentials)
.pipe(
tap(response => {
localStorage.setItem('token', response.token);
this.currentUser.set(response.user);
this.isAuthenticated.set(true);
})
);
}
logout() {
localStorage.removeItem('token');
this.currentUser.set(null);
this.isAuthenticated.set(false);
this.router.navigate(['/login']);
}
}
CRUD Operations
@Injectable({ providedIn: 'root' })
export class ProductService {
private http = inject(HttpClient);
private apiUrl = '/api/products';
getAll(): Observable<Product[]> {
return this.http.get<Product[]>(this.apiUrl);
}
getById(id: number): Observable<Product> {
return this.http.get<Product>(`${this.apiUrl}/${id}`);
}
create(product: Omit<Product, 'id'>): Observable<Product> {
return this.http.post<Product>(this.apiUrl, product);
}
update(id: number, product: Partial<Product>): Observable<Product> {
return this.http.patch<Product>(`${this.apiUrl}/${id}`, product);
}
delete(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}
Real-time Search
@Component({
selector: 'app-search',
standalone: true,
imports: [ReactiveFormsModule, CommonModule],
template: `
<input [formControl]="searchControl" placeholder="Search...">
@for (result of results$ | async; track result.id) {
<div>{{ result.name }}</div>
}
`
})
export class SearchComponent {
private searchService = inject(SearchService);
searchControl = new FormControl('');
results$ = this.searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(query =>
query ? this.searchService.search(query) : of([])
)
);
}
Testing Patterns
Component Test
describe('UserListComponent', () => {
let component: UserListComponent;
let fixture: ComponentFixture<UserListComponent>;
let userService: jasmine.SpyObj<UserService>;
beforeEach(async () => {
const spy = jasmine.createSpyObj('UserService', ['getUsers']);
await TestBed.configureTestingModule({
imports: [UserListComponent],
providers: [{ provide: UserService, useValue: spy }]
}).compileComponents();
userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
fixture = TestBed.createComponent(UserListComponent);
component = fixture.componentInstance;
});
it('should load users', () => {
const mockUsers = [{ id: 1, name: 'John' }];
userService.getUsers.and.returnValue(of(mockUsers));
fixture.detectChanges();
expect(component.users.length).toBe(1);
});
});
Service Test
describe('DataService', () => {
let service: DataService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [DataService]
});
service = TestBed.inject(DataService);
httpMock = TestBed.inject(HttpTestingController);
});
it('should fetch data', () => {
const mockData = [{ id: 1 }];
service.getData().subscribe(data => {
expect(data).toEqual(mockData);
});
const req = httpMock.expectOne('/api/data');
expect(req.request.method).toBe('GET');
req.flush(mockData);
});
});
Resources
- Official Angular Documentation
- Angular Blog
- Angular GitHub Repository
- RxJS Documentation
- Angular Style Guide
Context7 Integration
This skill is based on comprehensive research from the official Angular repository (Context7 Trust Score: 8.9), incorporating:
- Latest standalone component patterns
- Modern dependency injection with inject()
- Signal-based reactive state management
- New control flow syntax (@if, @for, @switch)
- Lazy loading best practices
- Reactive forms patterns
- RxJS integration strategies
- Performance optimization techniques
- Testing methodologies
- Migration paths from legacy patterns
All examples and patterns follow the official Angular team's recommendations and best practices, ensuring your Angular applications are built with industry-standard approaches that are maintainable, performant, and future-proof.