Agent skill
angular-framework
Develop enterprise-scale applications with Angular 18+ using dependency injection, RxJS, signals, and modular architecture. Master standalone components, services, directives, pipes, and reactive programming. Use for: building large-scale enterprise applications, implementing complex forms with reactive validation, managing application state with RxJS observables, creating reusable component libraries, integrating with REST APIs and GraphQL, implementing authentication and authorization, optimizing performance with change detection strategies, and deploying production-ready Angular applications.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/angular-framework
SKILL.md
Angular Framework
Build enterprise-scale applications with Angular 18+ using TypeScript, RxJS, and modern architecture patterns.
Overview
Angular is a comprehensive TypeScript-based framework for building scalable web applications. Angular 18 introduces standalone components as the default, improved dependency injection with the inject() function, signals for fine-grained reactivity, and enhanced developer experience with better tooling and performance.
Standalone Components
Component Structure
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-user-profile',
standalone: true,
imports: [CommonModule],
template: \`
<div class="profile">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>
\`,
styles: [\`
.profile {
padding: 1rem;
border: 1px solid #ccc;
}
\`]
})
export class UserProfileComponent {
user = {
name: 'John Doe',
email: '[email protected]'
};
}
Component Lifecycle
import { Component, OnInit, OnDestroy, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-lifecycle',
standalone: true,
template: '<p>Lifecycle Demo</p>'
})
export class LifecycleComponent implements OnInit, OnDestroy, AfterViewInit {
ngOnInit() {
console.log('Component initialized');
}
ngAfterViewInit() {
console.log('View initialized');
}
ngOnDestroy() {
console.log('Component destroyed');
}
}
Dependency Injection
Using inject() Function
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { UserService } from './user.service';
@Component({
selector: 'app-users',
standalone: true,
template: '<div>Users</div>'
})
export class UsersComponent {
private http = inject(HttpClient);
private userService = inject(UserService);
loadUsers() {
this.userService.getUsers().subscribe(users => {
console.log(users);
});
}
}
Creating Services
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserService {
private http = inject(HttpClient);
private apiUrl = '/api/users';
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getUserById(id: string): Observable<User> {
return this.http.get<User>(\`\${this.apiUrl}/\${id}\`);
}
createUser(user: User): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}
}
Hierarchical Injectors
// Root level
@Injectable({ providedIn: 'root' })
export class GlobalService {}
// Component level
@Component({
selector: 'app-feature',
standalone: true,
providers: [FeatureService]
})
export class FeatureComponent {}
Signals (New in Angular 16+)
Signal Basics
import { Component, signal, computed, effect } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: \`
<div>
<p>Count: {{ count() }}</p>
<p>Double: {{ doubleCount() }}</p>
<button (click)="increment()">Increment</button>
</div>
\`
})
export class CounterComponent {
count = signal(0);
doubleCount = computed(() => this.count() * 2);
constructor() {
effect(() => {
console.log(\`Count changed to \${this.count()}\`);
});
}
increment() {
this.count.update(value => value + 1);
}
}
Signal Arrays and Objects
import { signal } from '@angular/core';
// Array signal
const items = signal<string[]>([]);
// Add item
items.update(current => [...current, 'new item']);
// Remove item
items.update(current => current.filter(item => item !== 'remove me'));
// Object signal
const user = signal({ name: 'John', age: 30 });
// Update property
user.update(current => ({ ...current, age: 31 }));
Reactive Programming with RxJS
Observable Patterns
import { Component, inject, OnInit } from '@angular/core';
import { Observable, map, filter, debounceTime, switchMap } from 'rxjs';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-search',
standalone: true,
template: \`
<input [formControl]="searchControl" placeholder="Search...">
<div *ngFor="let result of results$ | async">
{{ result.name }}
</div>
\`
})
export class SearchComponent implements OnInit {
searchControl = new FormControl('');
results$!: Observable<SearchResult[]>;
ngOnInit() {
this.results$ = this.searchControl.valueChanges.pipe(
debounceTime(300),
filter(query => query.length > 2),
switchMap(query => this.searchService.search(query))
);
}
}
Common RxJS Operators
import { of, from, interval } from 'rxjs';
import { map, filter, take, mergeMap, catchError } from 'rxjs/operators';
// map - transform values
of(1, 2, 3).pipe(
map(x => x * 2)
).subscribe(console.log); // 2, 4, 6
// filter - filter values
of(1, 2, 3, 4).pipe(
filter(x => x % 2 === 0)
).subscribe(console.log); // 2, 4
// mergeMap - flatten inner observables
of('user1', 'user2').pipe(
mergeMap(id => this.userService.getUser(id))
).subscribe(console.log);
// catchError - handle errors
this.http.get('/api/data').pipe(
catchError(error => of({ error: true }))
).subscribe();
Forms
Reactive Forms
import { Component, inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-user-form',
standalone: true,
imports: [ReactiveFormsModule],
template: \`
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<input formControlName="name" placeholder="Name">
<div *ngIf="userForm.get('name')?.invalid && userForm.get('name')?.touched">
Name is required
</div>
<input formControlName="email" type="email" placeholder="Email">
<div *ngIf="userForm.get('email')?.invalid && userForm.get('email')?.touched">
Valid email required
</div>
<button type="submit" [disabled]="userForm.invalid">Submit</button>
</form>
\`
})
export class UserFormComponent {
private fb = inject(FormBuilder);
userForm: FormGroup = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
age: [null, [Validators.min(0), Validators.max(120)]]
});
onSubmit() {
if (this.userForm.valid) {
console.log(this.userForm.value);
}
}
}
Custom Validators
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export function passwordMatchValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const password = control.get('password');
const confirmPassword = control.get('confirmPassword');
if (!password || !confirmPassword) {
return null;
}
return password.value === confirmPassword.value
? null
: { passwordMismatch: true };
};
}
// Usage
this.form = this.fb.group({
password: ['', Validators.required],
confirmPassword: ['', Validators.required]
}, { validators: passwordMatchValidator() });
Directives
Structural Directives
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appUnless]',
standalone: true
})
export class UnlessDirective {
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {}
@Input() set appUnless(condition: boolean) {
if (!condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
// Usage
<div *appUnless="isHidden">Content</div>
Attribute Directives
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
@Input() appHighlight = 'yellow';
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.appHighlight);
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight('');
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
// Usage
<p appHighlight="lightblue">Hover me</p>
Pipes
Built-in Pipes
// Template
{{ value | date:'short' }}
{{ price | currency:'USD' }}
{{ text | uppercase }}
{{ items | json }}
{{ data | async }}
Custom Pipes
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate',
standalone: true
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit: number = 50): string {
return value.length > limit
? value.substring(0, limit) + '...'
: value;
}
}
// Usage
{{ longText | truncate:100 }}
Change Detection
OnPush Strategy
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
@Component({
selector: 'app-optimized',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<div>{{ data }}</div>'
})
export class OptimizedComponent {
@Input() data!: string;
}
Routing
import { Routes } from '@angular/router';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'users/:id', component: UserDetailComponent },
{
path: 'admin',
loadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent),
canActivate: [authGuard]
}
];
// Guard
export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService);
return authService.isAuthenticated();
};
Using the Reference Files
When to Read Each Reference
/references/dependency-injection.md — Read when implementing advanced DI patterns, custom providers, or hierarchical injectors.
/references/rxjs-patterns.md — Read when working with complex observable streams, error handling, or state management.
/references/forms-validation.md — Read when building complex forms, custom validators, or dynamic form controls.
/references/performance-optimization.md — Read when optimizing change detection, lazy loading, or bundle size.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?