🚀 TypeScript Advanced Patterns (03-04)
Master Type-Level Programming for Enterprise Angular Development - Elevate your TypeScript skills beyond the basics with advanced patterns that distinguish senior developers in technical interviews.
🎯 CHAPTER OVERVIEW
💡 Why Advanced TypeScript Patterns Matter
In today's competitive Angular development landscape, basic TypeScript knowledge isn't enough. Senior developers are expected to leverage TypeScript's advanced type system to:
- Build Type-Safe APIs that prevent runtime errors at compile time
- Create Reusable Generic Components that work across different data types
- Implement Custom Decorators for cross-cutting concerns and metadata
- Design Flexible Architecture using advanced type patterns
- Optimize Performance through intelligent type-level computations
🎪 Interview Reality Check
Company Tier Analysis:
- Tier 1 (Google, Microsoft, Meta): Advanced type patterns in 90% of interviews
- Tier 2 (Netflix, Uber, Airbnb): Generic programming and decorators in 75% of interviews
- Tier 3 (Startups, Agencies): Practical type safety patterns in 60% of interviews
Common Interview Scenarios: - "Design a type-safe configuration system for our Angular app" - "Implement a custom decorator for caching service methods" - "Create utility types that enforce business rules at compile time" - "Build a generic data access layer with full type inference"
📚 LEARNING OBJECTIVES
By the end of this chapter, you will master:
🔧 Advanced Generic Programming
- [ ] Conditional types for dynamic type behavior
- [ ] Mapped types for object transformation
- [ ] Template literal types for string manipulation
- [ ] Utility type creation and composition
🎭 Decorator Pattern Mastery
- [ ] Class decorators for component enhancement
- [ ] Method decorators for cross-cutting concerns
- [ ] Property decorators for metadata and validation
- [ ] Angular-specific decorator implementations
⚗️ Type-Level Programming
- [ ] Recursive types for complex data structures
- [ ] Type-level computations and algorithms
- [ ] Parser types for compile-time validation
- [ ] Advanced type manipulation techniques
🏗️ Angular Enterprise Integration
- [ ] Service layer typing strategies
- [ ] Component interface design patterns
- [ ] Guard and interceptor type safety
- [ ] Dependency injection with advanced types
🚀 Performance & Optimization
- [ ] Compile-time optimizations
- [ ] Type narrowing strategies
- [ ] Memory-efficient type patterns
- [ ] Bundle size impact considerations
🔥 INTERVIEW IMPACT ANALYSIS
📊 Market Research Insights
Salary Impact: - Advanced TypeScript skills correlate with $15,000-25,000 higher salaries - Senior positions increasingly require type-level programming expertise - 95% of Angular Lead roles expect advanced TypeScript knowledge
Interview Question Frequency: - Generic programming: High (85% of interviews) - Decorator patterns: Medium-High (70% of interviews) - Type-level programming: Medium (55% of senior interviews) - Angular integration: Very High (95% of Angular interviews)
Skills Gap Analysis: - Most developers know basic generics but struggle with conditional types - Decorator implementation separates junior from mid-level developers - Type-level programming distinguishes senior from lead developers
🛠️ PREREQUISITE KNOWLEDGE
Before diving into advanced patterns, ensure you're comfortable with:
✅ TypeScript Fundamentals (From 03-01)
- Basic types and interfaces
- Generic basics
- Union and intersection types
- Function overloading
✅ JavaScript Core Concepts (From 03-02)
- ES6+ features
- Closures and prototypes
- Class inheritance
- Module systems
✅ Angular Foundations
- Component and service architecture
- Dependency injection basics
- Lifecycle hooks
- Basic reactive patterns
Self-Assessment Quiz:
// Can you predict the types without running TypeScript?
// Question 1: What's the type of 'result'?
function getValue<T>(input: T): T extends string ? number : boolean {
return null as any;
}
const result = getValue("hello");
// Question 2: What properties does 'config' have?
type Config<T> = {
[K in keyof T]: T[K] extends Function ? never : T[K];
}
interface User { name: string; getId: () => number; age: number; }
type config = Config<User>;
// Question 3: Can this decorator be applied to methods?
function Cache(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// Implementation
}
Expected Answers:
1. result has type number (conditional type resolves)
2. config has type { name: string; age: number } (functions excluded)
3. Yes, it's a method decorator (has PropertyDescriptor parameter)
🗺️ CHAPTER ROADMAP
Phase 1: Advanced Generics Foundation (20 minutes)
- Conditional types deep dive
- Mapped type transformations
- Template literal type magic
- Real-world utility type creation
Phase 2: Decorator Pattern Mastery (25 minutes)
- Decorator theory and implementation
- Angular-specific decorator patterns
- Cross-cutting concern examples
- Performance considerations
Phase 3: Type-Level Programming (30 minutes)
- Recursive type algorithms
- Compile-time computations
- Parser type implementations
- Advanced type gymnastics
Phase 4: Angular Enterprise Integration (25 minutes)
- Service layer architecture
- Component typing strategies
- Guards and interceptors
- Dependency injection patterns
Phase 5: Company-Tier Challenges (40 minutes)
- Tier 1: Architecture-level type design
- Tier 2: Performance-optimized patterns
- Tier 3: Practical problem solving
Phase 6: Hands-On Implementation (30 minutes)
- Build your own utility types
- Create custom decorators
- Type-safe API design
- Interview-ready code examples
💰 STRATEGIC CAREER VALUE
🎯 Immediate Benefits
- Interview Confidence: Handle any TypeScript question with expertise
- Code Quality: Write more maintainable, error-free applications
- Team Leadership: Guide teams in advanced TypeScript adoption
- Problem Solving: Approach complex typing challenges systematically
📈 Long-Term Career Growth
- Senior Role Readiness: Meet expectations for advanced TypeScript skills
- Architecture Discussions: Contribute meaningfully to technical decisions
- Mentoring Ability: Help junior developers level up their TypeScript skills
- Innovation Capacity: Create novel solutions using advanced type patterns
🏆 Competitive Advantages
- Rare Skill Set: Advanced TypeScript knowledge is uncommon
- Full-Stack Readiness: Advanced typing applies to Node.js, React, Vue
- Future-Proof Skills: TypeScript adoption continues growing rapidly
- Interview Differentiation: Stand out from candidates with basic knowledge
🔧 ADVANCED GENERIC PROGRAMMING
🎯 Conditional Types: Dynamic Type Behavior
Conditional types enable TypeScript to choose types based on conditions, creating intelligent APIs that adapt to their usage.
💡 Foundation: Basic Conditional Types
// Basic syntax: T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
// Example usage
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
type Test3 = IsString<"hello">; // true
// Real-world example: API response typing
type ApiResponse<T> = T extends string
? { message: T; status: 'text' }
: T extends number
? { code: T; status: 'numeric' }
: { data: T; status: 'object' };
// Usage in Angular service
@Injectable()
export class ApiService {
send<T>(payload: T): Observable<ApiResponse<T>> {
return this.http.post<ApiResponse<T>>('/api', payload);
}
}
// TypeScript infers the correct response type
service.send("Hello").subscribe(res => {
// res.message is string, res.status is 'text'
});
service.send(404).subscribe(res => {
// res.code is number, res.status is 'numeric'
});
⚡ Advanced Conditional Patterns
// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumber = string | number;
type Result = ToArray<StringOrNumber>; // string[] | number[]
// Non-distributive (using tuple trick)
type ToArrayNonDistributive<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNonDistributive<StringOrNumber>; // (string | number)[]
// Complex conditional chains for Angular forms
type FormControlType<T> =
T extends string ? FormControl<string>
: T extends number ? FormControl<number>
: T extends boolean ? FormControl<boolean>
: T extends Date ? FormControl<Date>
: T extends Array<infer U> ? FormArray<FormControlType<U>>
: T extends object ? FormGroup<{ [K in keyof T]: FormControlType<T[K]> }>
: FormControl<T>;
// Example: User form typing
interface User {
name: string;
age: number;
active: boolean;
birthDate: Date;
tags: string[];
profile: {
bio: string;
avatar: string;
};
}
type UserFormType = FormControlType<User>;
// Results in properly typed form structure:
// FormGroup<{
// name: FormControl<string>;
// age: FormControl<number>;
// active: FormControl<boolean>;
// birthDate: FormControl<Date>;
// tags: FormArray<FormControl<string>>;
// profile: FormGroup<{
// bio: FormControl<string>;
// avatar: FormControl<string>;
// }>;
// }>
🏗️ Conditional Type Utilities for Angular
// Extract function parameters for dependency injection
type ExtractParams<T> = T extends (...args: infer P) => any ? P : never;
// Create injectable service type
type InjectableService<T> = T extends abstract new (...args: infer P) => infer R
? { provide: T; useClass: new (...args: P) => R; deps: P }
: never;
// Route parameter extraction
type ExtractRouteParams<T extends string> =
T extends `${infer Start}/:${infer Param}/${infer Rest}`
? { [K in Param]: string } & ExtractRouteParams<`${Start}/${Rest}`>
: T extends `${infer Start}/:${infer Param}`
? { [K in Param]: string }
: {};
type UserRoute = ExtractRouteParams<'/users/:id/posts/:postId'>;
// Result: { id: string; postId: string }
// Angular component prop validation
type ComponentProps<T> = {
[K in keyof T]: T[K] extends Function
? never
: T[K] extends TemplateRef<any>
? never
: T[K] extends ViewContainerRef
? never
: T[K];
};
@Component({
selector: 'app-user-card',
template: '...'
})
export class UserCardComponent {
@Input() user!: User;
@Input() showActions!: boolean;
@Input() onEdit!: (user: User) => void;
@ViewChild('template') template!: TemplateRef<any>;
// Only props that should be bindable
static getBindableProps(): ComponentProps<UserCardComponent> {
return {} as any; // Type helper
}
}
🗺️ Mapped Types: Object Transformation
Mapped types transform every property in a type, enabling powerful object manipulations.
💡 Foundation: Basic Mapped Types
// Basic syntax: { [K in keyof T]: ... }
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type Partial<T> = {
[K in keyof T]?: T[K];
};
// Angular-specific: Component state management
interface ComponentState {
loading: boolean;
error: string | null;
data: User[];
selectedId: number | null;
}
// Create updater functions for each property
type StateUpdaters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
type ComponentUpdaters = StateUpdaters<ComponentState>;
// Result:
// {
// setLoading: (value: boolean) => void;
// setError: (value: string | null) => void;
// setData: (value: User[]) => void;
// setSelectedId: (value: number | null) => void;
// }
// Implementation in Angular component
@Component({
selector: 'app-user-list',
template: '...'
})
export class UserListComponent implements ComponentUpdaters {
private state: ComponentState = {
loading: false,
error: null,
data: [],
selectedId: null
};
setLoading(value: boolean): void {
this.state = { ...this.state, loading: value };
}
setError(value: string | null): void {
this.state = { ...this.state, error: value };
}
setData(value: User[]): void {
this.state = { ...this.state, data: value };
}
setSelectedId(value: number | null): void {
this.state = { ...this.state, selectedId: value };
}
}
⚡ Advanced Mapped Type Patterns
// Key filtering and transformation
type ApiEndpoints = {
getUsers: () => Promise<User[]>;
createUser: (user: Partial<User>) => Promise<User>;
updateUser: (id: number, user: Partial<User>) => Promise<User>;
deleteUser: (id: number) => Promise<void>;
getUserById: (id: number) => Promise<User>;
};
// Extract only GET operations
type GetOperations<T> = {
[K in keyof T as K extends `get${string}` ? K : never]: T[K];
};
type GetEndpoints = GetOperations<ApiEndpoints>;
// Result: { getUsers: () => Promise<User[]>; getUserById: (id: number) => Promise<User>; }
// Transform functions to observables for Angular
type ObservableApi<T> = {
[K in keyof T]: T[K] extends (...args: infer P) => Promise<infer R>
? (...args: P) => Observable<R>
: never;
};
// Create Angular service interface
interface AngularUserService extends ObservableApi<ApiEndpoints> {}
// Implementation
@Injectable()
export class UserService implements AngularUserService {
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users');
}
createUser(user: Partial<User>): Observable<User> {
return this.http.post<User>('/api/users', user);
}
// ... other methods
}
// Advanced: Create type-safe form builders
type FormBuilderType<T> = {
[K in keyof T]: T[K] extends Array<infer U>
? FormArray<FormBuilderType<U>>
: T[K] extends object
? FormGroup<FormBuilderType<T[K]>>
: FormControl<T[K]>;
};
// Validation rule mapping
type ValidationRules<T> = {
[K in keyof T]?: T[K] extends string
? { required?: boolean; minLength?: number; maxLength?: number; pattern?: RegExp }
: T[K] extends number
? { required?: boolean; min?: number; max?: number }
: T[K] extends boolean
? { required?: boolean }
: T[K] extends Date
? { required?: boolean; minDate?: Date; maxDate?: Date }
: never;
};
// Usage in Angular reactive forms
@Injectable()
export class TypeSafeFormBuilder {
createForm<T>(
initialValue: T,
validationRules?: ValidationRules<T>
): FormGroup<FormBuilderType<T>> {
// Implementation would create properly typed form
return {} as FormGroup<FormBuilderType<T>>;
}
}
📝 Template Literal Types: String Manipulation
Template literal types enable compile-time string manipulation and validation.
💡 Foundation: Basic Template Literals
// Basic template literal types
type Greeting<T extends string> = `Hello, ${T}!`;
type Welcome = Greeting<"World">; // "Hello, World!"
// Angular route generation
type RouteParam<T extends string> = `:${T}`;
type UserRoute = `/users/${RouteParam<'id'>}`; // "/users/:id"
// Type-safe CSS class generation
type BEMElement<B extends string, E extends string> = `${B}__${E}`;
type BEMModifier<B extends string, M extends string> = `${B}--${M}`;
type ButtonClass = BEMElement<'btn', 'text'>; // "btn__text"
type ButtonModifier = BEMModifier<'btn', 'primary'>; // "btn--primary"
// Angular component class binding
@Component({
selector: 'app-button',
template: `
<button [class]="buttonClasses">
<ng-content></ng-content>
</button>
`
})
export class ButtonComponent {
@Input() variant: 'primary' | 'secondary' | 'danger' = 'primary';
@Input() size: 'small' | 'medium' | 'large' = 'medium';
get buttonClasses(): string {
const base = 'btn';
const element: BEMElement<'btn', 'button'> = 'btn__button';
const variant: BEMModifier<'btn', typeof this.variant> = `btn--${this.variant}`;
const sizeClass: BEMModifier<'btn', typeof this.size> = `btn--${this.size}`;
return `${base} ${element} ${variant} ${sizeClass}`;
}
}
⚡ Advanced Template Literal Patterns
// URL pattern parsing
type ParseUrlParams<T extends string> =
T extends `${infer Start}/:${infer Param}/${infer Rest}`
? { [K in Param]: string } & ParseUrlParams<Rest>
: T extends `${infer Start}/:${infer Param}`
? { [K in Param]: string }
: {};
// Angular route typing
type RouteParams<T extends string> = ParseUrlParams<T>;
interface TypedRoute<T extends string> {
path: T;
params: RouteParams<T>;
}
// Usage in Angular router
class TypedRouter {
navigate<T extends string>(route: TypedRoute<T>): void {
// Implementation would use Angular router
console.log(`Navigating to ${route.path} with params:`, route.params);
}
}
const router = new TypedRouter();
router.navigate({
path: '/users/:userId/posts/:postId',
params: { userId: '123', postId: '456' } // Fully typed!
});
// SQL-like query builder with template literals
type SelectClause<T, K extends keyof T> = `SELECT ${K extends string ? K : never}`;
type FromClause<T extends string> = `FROM ${T}`;
type WhereClause<T, K extends keyof T> = `WHERE ${K extends string ? K : never} = ?`;
type Query<T, TTable extends string, K extends keyof T, W extends keyof T> =
`${SelectClause<T, K>} ${FromClause<TTable>} ${WhereClause<T, W>}`;
// Type-safe query generation for Angular
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserQuery = Query<User, 'users', 'name' | 'email', 'id'>;
// Result: "SELECT name | email FROM users WHERE id = ?"
// Advanced: Type-safe event emitter
type EventMap = {
'user:created': { user: User };
'user:updated': { user: User; oldUser: User };
'user:deleted': { userId: number };
'system:error': { error: Error; context: string };
};
type EventName = keyof EventMap;
type EventPayload<T extends EventName> = EventMap[T];
class TypedEventEmitter {
emit<T extends EventName>(event: T, payload: EventPayload<T>): void {
// Implementation
}
on<T extends EventName>(
event: T,
handler: (payload: EventPayload<T>) => void
): void {
// Implementation
}
}
// Usage in Angular service
@Injectable()
export class UserEventService {
private emitter = new TypedEventEmitter();
onUserCreated(handler: (data: { user: User }) => void): void {
this.emitter.on('user:created', handler); // Fully typed!
}
emitUserCreated(user: User): void {
this.emitter.emit('user:created', { user }); // Type-safe payload!
}
}
🛠️ Custom Utility Type Creation
Building your own utility types for Angular-specific use cases.
💡 Angular Component Utilities
// Extract @Input properties from component
type ComponentInputs<T> = {
[K in keyof T]: T[K] extends Function ? never : T[K];
};
// Extract @Output properties
type ComponentOutputs<T> = {
[K in keyof T]: T[K] extends EventEmitter<infer U> ? EventEmitter<U> : never;
};
// Create component interface
type ComponentInterface<T> = {
inputs: ComponentInputs<T>;
outputs: ComponentOutputs<T>;
};
// Example component
@Component({
selector: 'app-user-form',
template: '...'
})
export class UserFormComponent {
@Input() user!: User;
@Input() readonly!: boolean;
@Output() save = new EventEmitter<User>();
@Output() cancel = new EventEmitter<void>();
// Internal methods not part of interface
private validateForm(): boolean { return true; }
handleSubmit(): void {}
}
type UserFormInterface = ComponentInterface<UserFormComponent>;
// Result:
// {
// inputs: { user: User; readonly: boolean };
// outputs: { save: EventEmitter<User>; cancel: EventEmitter<void> };
// }
⚡ Advanced Angular Utilities
// Service dependency extraction
type ExtractDependencies<T> = T extends new (...args: infer P) => any ? P : never;
// Type-safe service injection
class ServiceInjector {
static inject<T extends new (...args: any[]) => any>(
ServiceClass: T
): InstanceType<T> {
// Would integrate with Angular DI
return {} as InstanceType<T>;
}
}
// Guard return types
type GuardResult = boolean | Promise<boolean> | Observable<boolean>;
type ExtractGuardData<T> = T extends CanActivate
? ReturnType<T['canActivate']> extends GuardResult ? T : never
: never;
// Route configuration with type safety
interface TypedRoute<TComponent, TData = any> {
path: string;
component: new (...args: any[]) => TComponent;
data?: TData;
canActivate?: ExtractGuardData<any>[];
}
// HTTP interceptor typing
type InterceptorHandler<T> = (
req: HttpRequest<T>,
next: HttpHandler
) => Observable<HttpEvent<T>>;
type TypedInterceptor<T = any> = {
intercept: InterceptorHandler<T>;
};
// Form validation utilities
type ValidationErrorMap = { [key: string]: any };
type FormValidators<T> = {
[K in keyof T]?: (value: T[K]) => ValidationErrorMap | null;
};
type AsyncFormValidators<T> = {
[K in keyof T]?: (value: T[K]) => Observable<ValidationErrorMap | null>;
};
// Complete form type system
type TypedFormConfig<T> = {
initialValue: T;
validators?: FormValidators<T>;
asyncValidators?: AsyncFormValidators<T>;
disabled?: Partial<Record<keyof T, boolean>>;
};
// Usage in Angular forms
@Injectable()
export class TypedFormService {
createForm<T>(config: TypedFormConfig<T>): FormGroup<FormBuilderType<T>> {
// Implementation would create properly typed form
const formGroup = new FormGroup({} as any);
// Type-safe form control creation
Object.keys(config.initialValue as any).forEach(key => {
const typedKey = key as keyof T;
const initialValue = config.initialValue[typedKey];
const validator = config.validators?.[typedKey];
const asyncValidator = config.asyncValidators?.[typedKey];
const disabled = config.disabled?.[typedKey] || false;
// Create typed form control
const control = new FormControl(
{ value: initialValue, disabled },
validator as any,
asyncValidator as any
);
formGroup.addControl(key, control);
});
return formGroup as FormGroup<FormBuilderType<T>>;
}
}
// Real-world usage
interface UserRegistration {
email: string;
password: string;
confirmPassword: string;
age: number;
terms: boolean;
}
@Component({
selector: 'app-registration',
template: '...'
})
export class RegistrationComponent implements OnInit {
registrationForm!: FormGroup<FormBuilderType<UserRegistration>>;
constructor(private typedFormService: TypedFormService) {}
ngOnInit(): void {
this.registrationForm = this.typedFormService.createForm<UserRegistration>({
initialValue: {
email: '',
password: '',
confirmPassword: '',
age: 18,
terms: false
},
validators: {
email: (value) => this.validateEmail(value),
password: (value) => this.validatePassword(value),
age: (value) => this.validateAge(value)
},
asyncValidators: {
email: (value) => this.checkEmailAvailability(value)
}
});
}
private validateEmail(email: string): ValidationErrorMap | null {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email) ? null : { invalidEmail: true };
}
private validatePassword(password: string): ValidationErrorMap | null {
return password.length >= 8 ? null : { passwordTooShort: true };
}
private validateAge(age: number): ValidationErrorMap | null {
return age >= 18 ? null : { underAge: true };
}
private checkEmailAvailability(email: string): Observable<ValidationErrorMap | null> {
return this.http.get(`/api/check-email/${email}`).pipe(
map((result: any) => result.available ? null : { emailTaken: true }),
catchError(() => of(null))
);
}
}
🎭 DECORATOR PATTERN MASTERY
🎯 Understanding Decorators in TypeScript
Decorators provide a way to add annotations and meta-programming syntax for class declarations and members. In Angular, decorators are fundamental to the framework's architecture.
💡 Foundation: Decorator Basics
// Basic decorator syntax
function Component(options: ComponentOptions) {
return function <T extends new (...args: any[]) => any>(constructor: T) {
// Decorator logic here
return constructor;
};
}
// Understanding the decorator execution context
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with args:`, args);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} returned:`, result);
return result;
};
return descriptor;
}
// Angular component with custom decorator
@Component({
selector: 'app-user-service',
templateUrl: './user.component.html'
})
export class UserComponent {
@Log
getUserById(id: number): User {
return this.userService.getUser(id);
}
}
🏭 Class Decorators: Component Enhancement
Class decorators modify or replace the class definition, perfect for Angular component enhancement.
⚡ Advanced Class Decorator Patterns
// Performance monitoring decorator
interface PerformanceOptions {
logSlowOperations?: boolean;
threshold?: number;
includeMemory?: boolean;
}
function MonitorPerformance(options: PerformanceOptions = {}) {
return function <T extends new (...args: any[]) => any>(constructor: T) {
return class extends constructor {
private performanceMonitor = {
threshold: options.threshold || 100,
logSlow: options.logSlowOperations ?? true,
includeMemory: options.includeMemory ?? false
};
constructor(...args: any[]) {
super(...args);
if (this.performanceMonitor.includeMemory) {
this.monitorMemoryUsage();
}
}
private monitorMemoryUsage(): void {
setInterval(() => {
if (performance.memory) {
const used = performance.memory.usedJSHeapSize / 1024 / 1024;
console.log(`Component ${constructor.name} memory usage: ${used.toFixed(2)} MB`);
}
}, 5000);
}
};
};
}
// Error boundary decorator for Angular components
function ErrorBoundary(fallbackComponent?: Type<any>) {
return function <T extends new (...args: any[]) => any>(constructor: T) {
return class extends constructor implements OnDestroy {
private errorHandler = inject(ErrorHandler);
private vcRef = inject(ViewContainerRef);
private componentRef: ComponentRef<any> | null = null;
constructor(...args: any[]) {
super(...args);
this.setupErrorHandling();
}
private setupErrorHandling(): void {
// Wrap all methods with error handling
const prototype = Object.getPrototypeOf(this);
Object.getOwnPropertyNames(prototype).forEach(method => {
if (typeof this[method] === 'function' && method !== 'constructor') {
const originalMethod = this[method];
this[method] = (...args: any[]) => {
try {
return originalMethod.apply(this, args);
} catch (error) {
this.handleError(error);
}
};
}
});
}
private handleError(error: any): void {
console.error(`Error in component ${constructor.name}:`, error);
this.errorHandler.handleError(error);
if (fallbackComponent) {
this.showFallbackComponent();
}
}
private showFallbackComponent(): void {
if (this.componentRef) {
this.componentRef.destroy();
}
this.componentRef = this.vcRef.createComponent(fallbackComponent!);
}
ngOnDestroy(): void {
if (this.componentRef) {
this.componentRef.destroy();
}
if (super.ngOnDestroy) {
super.ngOnDestroy();
}
}
};
};
}
// Usage in Angular components
@Component({
selector: 'app-user-dashboard',
templateUrl: './user-dashboard.component.html'
})
@MonitorPerformance({
logSlowOperations: true,
threshold: 50,
includeMemory: true
})
@ErrorBoundary(ErrorFallbackComponent)
export class UserDashboardComponent implements OnInit {
users: User[] = [];
constructor(private userService: UserService) {}
ngOnInit(): void {
this.loadUsers();
}
async loadUsers(): Promise<void> {
// Performance and error handling automatically applied
this.users = await this.userService.getUsers().toPromise();
}
onUserClick(user: User): void {
// Error boundary will catch any errors here
this.processUser(user);
}
private processUser(user: User): void {
// Simulated error for demonstration
if (user.id === -1) {
throw new Error('Invalid user ID');
}
console.log('Processing user:', user);
}
}
⚙️ Method Decorators: Cross-Cutting Concerns
Method decorators intercept method calls, perfect for implementing cross-cutting concerns like caching, validation, and authorization.
🚀 Advanced Method Decorator Patterns
// Intelligent caching decorator
interface CacheOptions {
ttl?: number; // Time to live in milliseconds
key?: string | ((args: any[]) => string);
storage?: 'memory' | 'session' | 'local';
invalidateOnError?: boolean;
}
function Cache(options: CacheOptions = {}) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const cacheMap = new Map<string, { value: any; timestamp: number }>();
descriptor.value = function (...args: any[]) {
const cacheKey = options.key
? (typeof options.key === 'function' ? options.key(args) : options.key)
: `${propertyKey}_${JSON.stringify(args)}`;
const now = Date.now();
const cached = cacheMap.get(cacheKey);
// Check if cache is valid
if (cached && (!options.ttl || (now - cached.timestamp) < options.ttl)) {
console.log(`Cache hit for ${propertyKey}:`, cacheKey);
return cached.value;
}
try {
const result = originalMethod.apply(this, args);
// Handle async results
if (result instanceof Promise) {
return result.then(asyncResult => {
cacheMap.set(cacheKey, { value: asyncResult, timestamp: now });
return asyncResult;
}).catch(error => {
if (options.invalidateOnError) {
cacheMap.delete(cacheKey);
}
throw error;
});
}
// Handle observables
if (result && typeof result.subscribe === 'function') {
return new Observable(subscriber => {
result.subscribe({
next: (value: any) => {
cacheMap.set(cacheKey, { value, timestamp: now });
subscriber.next(value);
},
error: (error: any) => {
if (options.invalidateOnError) {
cacheMap.delete(cacheKey);
}
subscriber.error(error);
},
complete: () => subscriber.complete()
});
});
}
// Synchronous result
cacheMap.set(cacheKey, { value: result, timestamp: now });
return result;
} catch (error) {
if (options.invalidateOnError) {
cacheMap.delete(cacheKey);
}
throw error;
}
};
return descriptor;
};
}
// Rate limiting decorator
interface RateLimitOptions {
maxCalls: number;
windowMs: number;
message?: string;
}
function RateLimit(options: RateLimitOptions) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const callTimestamps: number[] = [];
descriptor.value = function (...args: any[]) {
const now = Date.now();
const windowStart = now - options.windowMs;
// Remove old timestamps outside the window
while (callTimestamps.length > 0 && callTimestamps[0] < windowStart) {
callTimestamps.shift();
}
// Check rate limit
if (callTimestamps.length >= options.maxCalls) {
const error = new Error(
options.message ||
`Rate limit exceeded: ${options.maxCalls} calls per ${options.windowMs}ms`
);
throw error;
}
// Record this call
callTimestamps.push(now);
return originalMethod.apply(this, args);
};
return descriptor;
};
}
// Authorization decorator
interface AuthorizeOptions {
roles?: string[];
permissions?: string[];
allowSelf?: boolean;
selfProperty?: string;
}
function Authorize(options: AuthorizeOptions = {}) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
// Inject authentication service (in real implementation)
const authService = inject(AuthService);
const currentUser = authService.getCurrentUser();
if (!currentUser) {
throw new Error('Authentication required');
}
// Check roles
if (options.roles && options.roles.length > 0) {
const hasRole = options.roles.some(role =>
currentUser.roles.includes(role)
);
if (!hasRole) {
throw new Error('Insufficient role permissions');
}
}
// Check permissions
if (options.permissions && options.permissions.length > 0) {
const hasPermission = options.permissions.some(permission =>
currentUser.permissions.includes(permission)
);
if (!hasPermission) {
throw new Error('Insufficient permissions');
}
}
// Check self-access
if (options.allowSelf && options.selfProperty) {
const resourceUserId = args[0]?.[options.selfProperty];
if (resourceUserId && resourceUserId === currentUser.id) {
return originalMethod.apply(this, args);
}
}
return originalMethod.apply(this, args);
};
return descriptor;
};
}
// Usage in Angular service
@Injectable()
export class UserService {
constructor(private http: HttpClient) {}
@Cache({
ttl: 300000, // 5 minutes
key: (args) => `user_${args[0]}`,
storage: 'memory'
})
@RateLimit({
maxCalls: 10,
windowMs: 60000, // 1 minute
message: 'Too many user requests'
})
getUserById(id: number): Observable<User> {
return this.http.get<User>(`/api/users/${id}`);
}
@Authorize({
roles: ['admin', 'moderator'],
permissions: ['user:delete']
})
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`/api/users/${id}`);
}
@Authorize({
allowSelf: true,
selfProperty: 'id',
permissions: ['user:update']
})
updateUser(user: User): Observable<User> {
return this.http.put<User>(`/api/users/${user.id}`, user);
}
}
🏷️ Property Decorators: Metadata and Validation
Property decorators add metadata to class properties, essential for validation, serialization, and framework integration.
💫 Advanced Property Decorator Patterns
// Validation decorator system
interface ValidationRule {
message?: string;
validator: (value: any) => boolean;
}
const validationRules = new Map<any, Map<string, ValidationRule[]>>();
function Required(message?: string) {
return function (target: any, propertyKey: string) {
addValidationRule(target, propertyKey, {
message: message || `${propertyKey} is required`,
validator: (value) => value !== null && value !== undefined && value !== ''
});
};
}
function MinLength(length: number, message?: string) {
return function (target: any, propertyKey: string) {
addValidationRule(target, propertyKey, {
message: message || `${propertyKey} must be at least ${length} characters`,
validator: (value) => typeof value === 'string' && value.length >= length
});
};
}
function Email(message?: string) {
return function (target: any, propertyKey: string) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
addValidationRule(target, propertyKey, {
message: message || `${propertyKey} must be a valid email`,
validator: (value) => typeof value === 'string' && emailRegex.test(value)
});
};
}
function Range(min: number, max: number, message?: string) {
return function (target: any, propertyKey: string) {
addValidationRule(target, propertyKey, {
message: message || `${propertyKey} must be between ${min} and ${max}`,
validator: (value) => typeof value === 'number' && value >= min && value <= max
});
};
}
function addValidationRule(target: any, propertyKey: string, rule: ValidationRule): void {
if (!validationRules.has(target.constructor)) {
validationRules.set(target.constructor, new Map());
}
const classRules = validationRules.get(target.constructor)!;
if (!classRules.has(propertyKey)) {
classRules.set(propertyKey, []);
}
classRules.get(propertyKey)!.push(rule);
}
// Validation utility
class Validator {
static validate(instance: any): ValidationResult {
const errors: string[] = [];
const constructor = instance.constructor;
const classRules = validationRules.get(constructor);
if (!classRules) {
return { isValid: true, errors: [] };
}
for (const [propertyKey, rules] of classRules) {
const value = instance[propertyKey];
for (const rule of rules) {
if (!rule.validator(value)) {
errors.push(rule.message || `Validation failed for ${propertyKey}`);
}
}
}
return { isValid: errors.length === 0, errors };
}
}
interface ValidationResult {
isValid: boolean;
errors: string[];
}
// Serialization decorators
interface SerializationOptions {
name?: string;
transformer?: (value: any) => any;
exclude?: boolean;
}
const serializationMetadata = new Map<any, Map<string, SerializationOptions>>();
function Serialize(options: SerializationOptions = {}) {
return function (target: any, propertyKey: string) {
if (!serializationMetadata.has(target.constructor)) {
serializationMetadata.set(target.constructor, new Map());
}
serializationMetadata.get(target.constructor)!.set(propertyKey, options);
};
}
function Exclude() {
return Serialize({ exclude: true });
}
function Transform(transformer: (value: any) => any) {
return Serialize({ transformer });
}
// Serialization utility
class Serializer {
static toJSON(instance: any): any {
const result: any = {};
const metadata = serializationMetadata.get(instance.constructor);
for (const key in instance) {
if (instance.hasOwnProperty(key)) {
const options = metadata?.get(key) || {};
if (options.exclude) {
continue;
}
const serializedKey = options.name || key;
let value = instance[key];
if (options.transformer) {
value = options.transformer(value);
}
result[serializedKey] = value;
}
}
return result;
}
}
// Usage in Angular models
export class User {
@Required('User ID is required')
@Serialize({ name: 'user_id' })
id!: number;
@Required('Name is required')
@MinLength(2, 'Name must be at least 2 characters')
@Serialize()
name!: string;
@Required('Email is required')
@Email('Please enter a valid email address')
@Serialize()
email!: string;
@Range(18, 120, 'Age must be between 18 and 120')
@Serialize()
age!: number;
@Transform((date: Date) => date.toISOString())
@Serialize({ name: 'created_at' })
createdAt!: Date;
@Exclude()
password!: string;
@Exclude()
private internalId: string = Math.random().toString();
// Validation method
validate(): ValidationResult {
return Validator.validate(this);
}
// Serialization method
toJSON(): any {
return Serializer.toJSON(this);
}
}
// Angular component using decorated model
@Component({
selector: 'app-user-form',
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<div *ngFor="let error of validationErrors" class="error">
{{ error }}
</div>
<input formControlName="name" placeholder="Name" />
<input formControlName="email" placeholder="Email" />
<input formControlName="age" type="number" placeholder="Age" />
<button type="submit" [disabled]="!userForm.valid">Save</button>
</form>
`
})
export class UserFormComponent implements OnInit {
userForm!: FormGroup;
validationErrors: string[] = [];
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.userForm = this.fb.group({
name: [''],
email: [''],
age: [18]
});
}
onSubmit(): void {
const formValue = this.userForm.value;
const user = new User();
// Map form values to user instance
Object.assign(user, formValue);
user.id = Date.now(); // Generate ID
user.createdAt = new Date();
user.password = 'temp-password'; // Would be handled securely
// Validate using decorators
const validation = user.validate();
if (!validation.isValid) {
this.validationErrors = validation.errors;
return;
}
// Serialize for API call
const payload = user.toJSON();
console.log('Sending to API:', payload);
// Clear errors on success
this.validationErrors = [];
}
}
🔧 Parameter Decorators: Dependency Injection Enhancement
Parameter decorators modify function parameters, commonly used for dependency injection customization in Angular.
⚡ Advanced Parameter Decorator Patterns
// Custom injection decorators
function InjectConfig(key: string) {
return function (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
// Store metadata for custom injection
const existingTokens = Reflect.getMetadata('custom:inject:tokens', target) || [];
existingTokens[parameterIndex] = { type: 'config', key };
Reflect.defineMetadata('custom:inject:tokens', existingTokens, target);
};
}
function InjectLogger(category?: string) {
return function (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
const existingTokens = Reflect.getMetadata('custom:inject:tokens', target) || [];
existingTokens[parameterIndex] = { type: 'logger', category };
Reflect.defineMetadata('custom:inject:tokens', existingTokens, target);
};
}
// Enhanced Angular service with parameter decorators
@Injectable()
export class EnhancedUserService {
constructor(
private http: HttpClient,
@InjectConfig('api.baseUrl') private apiBaseUrl: string,
@InjectLogger('UserService') private logger: Logger,
@Inject('CACHE_CONFIG') private cacheConfig: CacheConfig
) {
this.logger.info('UserService initialized', { apiBaseUrl: this.apiBaseUrl });
}
getUsers(): Observable<User[]> {
this.logger.debug('Fetching users');
return this.http.get<User[]>(`${this.apiBaseUrl}/users`);
}
}
🎯 ANGULAR INTEGRATION PATTERNS
🏗️ Service Typing and Dependency Injection
Advanced TypeScript patterns for building robust, type-safe Angular services with sophisticated dependency injection.
💡 Foundation: Type-Safe Service Architecture
// Abstract service base with generic typing
abstract class BaseService<T, K extends keyof T = keyof T> {
protected abstract endpoint: string;
protected abstract http: HttpClient;
// Generic CRUD operations with full type safety
getAll(): Observable<T[]> {
return this.http.get<T[]>(this.endpoint);
}
getById(id: T[K]): Observable<T> {
return this.http.get<T>(`${this.endpoint}/${id}`);
}
create(entity: Omit<T, K>): Observable<T> {
return this.http.post<T>(this.endpoint, entity);
}
update(id: T[K], entity: Partial<T>): Observable<T> {
return this.http.put<T>(`${this.endpoint}/${id}`, entity);
}
delete(id: T[K]): Observable<void> {
return this.http.delete<void>(`${this.endpoint}/${id}`);
}
// Advanced query builder with type safety
query<R = T>(builder: QueryBuilder<T>): Observable<R[]> {
const queryString = this.buildQueryString(builder);
return this.http.get<R[]>(`${this.endpoint}?${queryString}`);
}
private buildQueryString<T>(builder: QueryBuilder<T>): string {
const params = new URLSearchParams();
if (builder.select) {
params.append('select', builder.select.join(','));
}
if (builder.where) {
Object.entries(builder.where).forEach(([key, value]) => {
params.append(`filter[${key}]`, String(value));
});
}
if (builder.orderBy) {
const orderString = builder.orderBy
.map(order => `${String(order.field)}:${order.direction}`)
.join(',');
params.append('sort', orderString);
}
if (builder.limit) {
params.append('limit', String(builder.limit));
}
if (builder.offset) {
params.append('offset', String(builder.offset));
}
return params.toString();
}
}
// Type-safe query builder
interface QueryBuilder<T> {
select?: Array<keyof T>;
where?: Partial<T>;
orderBy?: Array<{
field: keyof T;
direction: 'asc' | 'desc';
}>;
limit?: number;
offset?: number;
}
// Concrete service implementation
interface User {
id: number;
email: string;
firstName: string;
lastName: string;
role: 'admin' | 'user' | 'moderator';
createdAt: Date;
profile?: UserProfile;
}
interface UserProfile {
avatar?: string;
bio?: string;
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
}
@Injectable({ providedIn: 'root' })
export class UserService extends BaseService<User, 'id'> {
protected endpoint = '/api/users';
constructor(protected http: HttpClient) {
super();
}
// Specialized methods with advanced typing
getUsersByRole(role: User['role']): Observable<User[]> {
return this.query({
where: { role },
orderBy: [{ field: 'lastName', direction: 'asc' }]
});
}
searchUsers(criteria: {
query?: string;
role?: User['role'];
createdAfter?: Date;
}): Observable<User[]> {
return this.query({
where: criteria as Partial<User>,
orderBy: [{ field: 'createdAt', direction: 'desc' }]
});
}
// Type-safe profile updates
updateProfile(
userId: User['id'],
profile: DeepPartial<UserProfile>
): Observable<User> {
return this.http.patch<User>(`${this.endpoint}/${userId}/profile`, profile);
}
}
// Advanced dependency injection with conditional services
type ServiceConfig = {
caching: boolean;
offline: boolean;
realtime: boolean;
};
type ConditionalServices<T extends ServiceConfig> = {
cacheService: T['caching'] extends true ? CacheService : never;
offlineService: T['offline'] extends true ? OfflineService : never;
realtimeService: T['realtime'] extends true ? RealtimeService : never;
};
// Factory pattern with conditional injection
export function createUserService<T extends ServiceConfig>(
config: T,
injector: Injector
): UserService & ConditionalServices<T> {
const baseService = new UserService(injector.get(HttpClient));
const services: any = { ...baseService };
if (config.caching) {
services.cacheService = injector.get(CacheService);
}
if (config.offline) {
services.offlineService = injector.get(OfflineService);
}
if (config.realtime) {
services.realtimeService = injector.get(RealtimeService);
}
return services;
}
// Usage in component
@Component({
selector: 'app-user-management',
template: '...'
})
export class UserManagementComponent implements OnInit {
private userService = createUserService(
{ caching: true, offline: false, realtime: true },
this.injector
);
constructor(private injector: Injector) {}
ngOnInit(): void {
// Service has both base methods and conditional services
this.userService.getAll(); // From base service
this.userService.cacheService.get('users'); // Available due to config
this.userService.realtimeService.subscribe('user-updates'); // Available
// this.userService.offlineService; // TypeScript error - not available
}
}
⚡ Advanced Service Patterns
// Multi-provider pattern with type safety
interface ApiProvider {
name: string;
get<T>(endpoint: string): Observable<T>;
post<T>(endpoint: string, data: any): Observable<T>;
}
class RestApiProvider implements ApiProvider {
name = 'REST';
constructor(private http: HttpClient) {}
get<T>(endpoint: string): Observable<T> {
return this.http.get<T>(endpoint);
}
post<T>(endpoint: string, data: any): Observable<T> {
return this.http.post<T>(endpoint, data);
}
}
class GraphQLProvider implements ApiProvider {
name = 'GraphQL';
constructor(private apollo: Apollo) {}
get<T>(query: string): Observable<T> {
return this.apollo.query<T>({ query: gql(query) })
.pipe(map(result => result.data));
}
post<T>(mutation: string, variables?: any): Observable<T> {
return this.apollo.mutate<T>({ mutation: gql(mutation), variables })
.pipe(map(result => result.data!));
}
}
// Service adapter pattern
@Injectable({ providedIn: 'root' })
export class AdaptiveApiService {
private provider: ApiProvider;
constructor(
@Inject('API_PROVIDER') providerType: 'REST' | 'GraphQL',
private http: HttpClient,
@Optional() private apollo?: Apollo
) {
this.provider = this.createProvider(providerType);
}
private createProvider(type: 'REST' | 'GraphQL'): ApiProvider {
switch (type) {
case 'REST':
return new RestApiProvider(this.http);
case 'GraphQL':
if (!this.apollo) {
throw new Error('Apollo client required for GraphQL provider');
}
return new GraphQLProvider(this.apollo);
default:
throw new Error(`Unknown provider type: ${type}`);
}
}
// Unified interface regardless of underlying provider
getUsers(): Observable<User[]> {
if (this.provider.name === 'REST') {
return this.provider.get<User[]>('/api/users');
} else {
return this.provider.get<User[]>(`
query GetUsers {
users {
id
email
firstName
lastName
role
}
}
`);
}
}
createUser(user: Omit<User, 'id' | 'createdAt'>): Observable<User> {
if (this.provider.name === 'REST') {
return this.provider.post<User>('/api/users', user);
} else {
return this.provider.post<User>(`
mutation CreateUser($input: UserInput!) {
createUser(input: $input) {
id
email
firstName
lastName
role
createdAt
}
}
`, { input: user });
}
}
}
// Module configuration with provider factory
@NgModule({
providers: [
{
provide: 'API_PROVIDER',
useFactory: (config: AppConfig) => config.apiType,
deps: [AppConfig]
},
AdaptiveApiService
]
})
export class ApiModule {}
🧩 Component Interface Design
Advanced patterns for creating flexible, reusable components with sophisticated type safety.
💡 Generic Component Architecture
// Base generic component interface
interface ComponentProps<T = any> {
data?: T;
loading?: boolean;
error?: string | null;
config?: ComponentConfig;
}
interface ComponentConfig {
theme?: 'light' | 'dark';
size?: 'small' | 'medium' | 'large';
variant?: string;
}
// Generic data table component
interface TableColumn<T> {
key: keyof T;
label: string;
sortable?: boolean;
width?: string;
formatter?: (value: T[keyof T], row: T) => string;
template?: TemplateRef<{ $implicit: T[keyof T]; row: T }>;
}
interface TableConfig<T> extends ComponentConfig {
selectable?: boolean;
multiSelect?: boolean;
pagination?: {
pageSize: number;
showSizeChanger?: boolean;
};
sorting?: {
field: keyof T;
direction: 'asc' | 'desc';
};
}
@Component({
selector: 'app-data-table',
template: `
<div class="data-table" [class]="themeClass">
<table>
<thead>
<tr>
<th *ngIf="config.selectable">
<input
type="checkbox"
[checked]="allSelected"
(change)="toggleAllSelection($event)"
*ngIf="config.multiSelect">
</th>
<th
*ngFor="let column of columns"
[style.width]="column.width"
[class.sortable]="column.sortable"
(click)="sort(column)">
{{ column.label }}
<span
*ngIf="isSorted(column)"
class="sort-indicator">
{{ getSortDirection(column) }}
</span>
</th>
</tr>
</thead>
<tbody>
<tr
*ngFor="let row of paginatedData; trackBy: trackByFn"
[class.selected]="isSelected(row)">
<td *ngIf="config.selectable">
<input
type="checkbox"
[checked]="isSelected(row)"
(change)="toggleSelection(row, $event)">
</td>
<td *ngFor="let column of columns">
<ng-container *ngIf="column.template; else defaultCell">
<ng-container
*ngTemplateOutlet="column.template; context: {
$implicit: getColumnValue(row, column),
row: row
}">
</ng-container>
</ng-container>
<ng-template #defaultCell>
{{ formatColumnValue(row, column) }}
</ng-template>
</td>
</tr>
</tbody>
</table>
<div class="pagination" *ngIf="config.pagination">
<!-- Pagination controls -->
</div>
</div>
`,
styleUrls: ['./data-table.component.scss']
})
export class DataTableComponent<T> implements OnInit, ComponentProps<T[]> {
@Input() data: T[] = [];
@Input() columns: TableColumn<T>[] = [];
@Input() config: TableConfig<T> = {};
@Input() loading = false;
@Input() error: string | null = null;
@Input() trackByFn: TrackByFunction<T> = (index) => index;
@Output() selectionChange = new EventEmitter<T[]>();
@Output() sortChange = new EventEmitter<{ field: keyof T; direction: 'asc' | 'desc' }>();
@Output() pageChange = new EventEmitter<{ page: number; pageSize: number }>();
selectedItems: T[] = [];
currentPage = 1;
sortConfig: { field: keyof T; direction: 'asc' | 'desc' } | null = null;
get themeClass(): string {
return `theme-${this.config.theme || 'light'} size-${this.config.size || 'medium'}`;
}
get allSelected(): boolean {
return this.data.length > 0 && this.selectedItems.length === this.data.length;
}
get paginatedData(): T[] {
if (!this.config.pagination) return this.sortedData;
const startIndex = (this.currentPage - 1) * this.config.pagination.pageSize;
const endIndex = startIndex + this.config.pagination.pageSize;
return this.sortedData.slice(startIndex, endIndex);
}
get sortedData(): T[] {
if (!this.sortConfig) return this.data;
return [...this.data].sort((a, b) => {
const aValue = a[this.sortConfig!.field];
const bValue = b[this.sortConfig!.field];
if (aValue < bValue) return this.sortConfig!.direction === 'asc' ? -1 : 1;
if (aValue > bValue) return this.sortConfig!.direction === 'asc' ? 1 : -1;
return 0;
});
}
ngOnInit(): void {
if (this.config.sorting) {
this.sortConfig = this.config.sorting;
}
}
toggleAllSelection(event: Event): void {
const checked = (event.target as HTMLInputElement).checked;
this.selectedItems = checked ? [...this.data] : [];
this.selectionChange.emit(this.selectedItems);
}
toggleSelection(item: T, event: Event): void {
const checked = (event.target as HTMLInputElement).checked;
if (this.config.multiSelect) {
if (checked) {
this.selectedItems = [...this.selectedItems, item];
} else {
this.selectedItems = this.selectedItems.filter(selected => selected !== item);
}
} else {
this.selectedItems = checked ? [item] : [];
}
this.selectionChange.emit(this.selectedItems);
}
isSelected(item: T): boolean {
return this.selectedItems.includes(item);
}
sort(column: TableColumn<T>): void {
if (!column.sortable) return;
const currentDirection = this.getSortDirection(column);
const newDirection = currentDirection === 'asc' ? 'desc' : 'asc';
this.sortConfig = { field: column.key, direction: newDirection };
this.sortChange.emit(this.sortConfig);
}
isSorted(column: TableColumn<T>): boolean {
return this.sortConfig?.field === column.key;
}
getSortDirection(column: TableColumn<T>): 'asc' | 'desc' {
return this.isSorted(column) ? this.sortConfig!.direction : 'asc';
}
getColumnValue(row: T, column: TableColumn<T>): T[keyof T] {
return row[column.key];
}
formatColumnValue(row: T, column: TableColumn<T>): string {
const value = this.getColumnValue(row, column);
return column.formatter ? column.formatter(value, row) : String(value);
}
}
// Usage with type safety
@Component({
selector: 'app-user-list',
template: `
<app-data-table
[data]="users"
[columns]="columns"
[config]="tableConfig"
[loading]="loading"
(selectionChange)="onSelectionChange($event)"
(sortChange)="onSortChange($event)">
</app-data-table>
`
})
export class UserListComponent implements OnInit {
users: User[] = [];
loading = false;
columns: TableColumn<User>[] = [
{ key: 'firstName', label: 'First Name', sortable: true },
{ key: 'lastName', label: 'Last Name', sortable: true },
{ key: 'email', label: 'Email', sortable: true },
{
key: 'role',
label: 'Role',
sortable: true,
formatter: (value) => (value as string).toUpperCase()
},
{
key: 'createdAt',
label: 'Created',
sortable: true,
formatter: (value) => new Date(value as Date).toLocaleDateString()
}
];
tableConfig: TableConfig<User> = {
theme: 'light',
size: 'medium',
selectable: true,
multiSelect: true,
pagination: {
pageSize: 10,
showSizeChanger: true
},
sorting: {
field: 'lastName',
direction: 'asc'
}
};
constructor(private userService: UserService) {}
ngOnInit(): void {
this.loadUsers();
}
loadUsers(): void {
this.loading = true;
this.userService.getAll().subscribe({
next: (users) => {
this.users = users;
this.loading = false;
},
error: (error) => {
console.error('Failed to load users:', error);
this.loading = false;
}
});
}
onSelectionChange(selectedUsers: User[]): void {
console.log('Selected users:', selectedUsers);
}
onSortChange(sort: { field: keyof User; direction: 'asc' | 'desc' }): void {
console.log('Sort changed:', sort);
}
}
⚡ Advanced Component Composition
// Higher-order component pattern
interface WithLoadingProps {
loading: boolean;
loadingText?: string;
}
interface WithErrorProps {
error: string | null;
onRetry?: () => void;
}
// Mixin pattern for component composition
type Constructor<T = {}> = new (...args: any[]) => T;
function WithLoading<TBase extends Constructor>(Base: TBase) {
return class extends Base implements WithLoadingProps {
@Input() loading = false;
@Input() loadingText = 'Loading...';
protected showLoading(): boolean {
return this.loading;
}
protected getLoadingTemplate(): TemplateRef<any> | null {
// Return loading template
return null;
}
};
}
function WithError<TBase extends Constructor>(Base: TBase) {
return class extends Base implements WithErrorProps {
@Input() error: string | null = null;
@Output() retry = new EventEmitter<void>();
protected hasError(): boolean {
return !!this.error;
}
protected onRetry(): void {
this.retry.emit();
}
};
}
// Composable base component
class BaseComponent {}
// Create enhanced component with mixins
@Component({
selector: 'app-enhanced-user-list',
template: `
<div class="component-wrapper">
<div *ngIf="showLoading()" class="loading">
{{ loadingText }}
</div>
<div *ngIf="hasError()" class="error">
{{ error }}
<button (click)="onRetry()">Retry</button>
</div>
<div *ngIf="!showLoading() && !hasError()" class="content">
<app-data-table
[data]="users"
[columns]="columns"
[config]="tableConfig">
</app-data-table>
</div>
</div>
`
})
export class EnhancedUserListComponent
extends WithError(WithLoading(BaseComponent))
implements OnInit {
users: User[] = [];
columns: TableColumn<User>[] = [];
tableConfig: TableConfig<User> = {};
constructor(private userService: UserService) {
super();
}
ngOnInit(): void {
this.loadUsers();
}
private loadUsers(): void {
this.loading = true;
this.error = null;
this.userService.getAll().subscribe({
next: (users) => {
this.users = users;
this.loading = false;
},
error: (error) => {
this.error = 'Failed to load users';
this.loading = false;
}
});
}
onRetry(): void {
this.loadUsers();
}
}
// Advanced form component with type safety
interface FormFieldConfig<T> {
key: keyof T;
label: string;
type: 'text' | 'email' | 'password' | 'number' | 'select' | 'checkbox';
required?: boolean;
validators?: ValidatorFn[];
options?: Array<{ value: any; label: string }>;
placeholder?: string;
disabled?: boolean;
}
@Component({
selector: 'app-dynamic-form',
template: `
<form [formGroup]="formGroup" (ngSubmit)="onSubmit()">
<div *ngFor="let field of fields" class="form-field">
<label [for]="field.key">{{ field.label }}</label>
<input
*ngIf="isInputField(field)"
[id]="field.key"
[type]="field.type"
[formControlName]="field.key"
[placeholder]="field.placeholder"
[disabled]="field.disabled">
<select
*ngIf="field.type === 'select'"
[id]="field.key"
[formControlName]="field.key"
[disabled]="field.disabled">
<option *ngFor="let option of field.options" [value]="option.value">
{{ option.label }}
</option>
</select>
<input
*ngIf="field.type === 'checkbox'"
type="checkbox"
[id]="field.key"
[formControlName]="field.key"
[disabled]="field.disabled">
<div
*ngIf="getFieldErrors(field.key)"
class="field-errors">
<span *ngFor="let error of getFieldErrors(field.key)">
{{ error }}
</span>
</div>
</div>
<button type="submit" [disabled]="formGroup.invalid">
Submit
</button>
</form>
`,
styleUrls: ['./dynamic-form.component.scss']
})
export class DynamicFormComponent<T> implements OnInit {
@Input() fields: FormFieldConfig<T>[] = [];
@Input() initialValue?: Partial<T>;
@Output() formSubmit = new EventEmitter<T>();
@Output() formChange = new EventEmitter<Partial<T>>();
formGroup!: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.createForm();
}
private createForm(): void {
const controls: { [key: string]: FormControl } = {};
for (const field of this.fields) {
const initialValue = this.initialValue?.[field.key] || null;
const validators = field.required
? [Validators.required, ...(field.validators || [])]
: field.validators || [];
controls[field.key as string] = new FormControl(
{ value: initialValue, disabled: field.disabled },
validators
);
}
this.formGroup = this.fb.group(controls);
// Emit changes
this.formGroup.valueChanges.subscribe(value => {
this.formChange.emit(value);
});
}
isInputField(field: FormFieldConfig<T>): boolean {
return ['text', 'email', 'password', 'number'].includes(field.type);
}
getFieldErrors(fieldKey: keyof T): string[] {
const control = this.formGroup.get(fieldKey as string);
if (!control || !control.errors || !control.touched) {
return [];
}
const errors: string[] = [];
if (control.errors['required']) {
const field = this.fields.find(f => f.key === fieldKey);
errors.push(`${field?.label} is required`);
}
if (control.errors['email']) {
errors.push('Please enter a valid email address');
}
if (control.errors['minlength']) {
const { requiredLength } = control.errors['minlength'];
errors.push(`Minimum length is ${requiredLength} characters`);
}
return errors;
}
onSubmit(): void {
if (this.formGroup.valid) {
this.formSubmit.emit(this.formGroup.value);
}
}
}
// Usage with full type safety
@Component({
selector: 'app-user-form',
template: `
<app-dynamic-form
[fields]="userFields"
[initialValue]="user"
(formSubmit)="onSubmit($event)"
(formChange)="onFormChange($event)">
</app-dynamic-form>
`
})
export class UserFormComponent {
@Input() user?: Partial<User>;
@Output() save = new EventEmitter<User>();
userFields: FormFieldConfig<User>[] = [
{
key: 'firstName',
label: 'First Name',
type: 'text',
required: true,
placeholder: 'Enter first name'
},
{
key: 'lastName',
label: 'Last Name',
type: 'text',
required: true,
placeholder: 'Enter last name'
},
{
key: 'email',
label: 'Email',
type: 'email',
required: true,
validators: [Validators.email],
placeholder: 'Enter email address'
},
{
key: 'role',
label: 'Role',
type: 'select',
required: true,
options: [
{ value: 'user', label: 'User' },
{ value: 'moderator', label: 'Moderator' },
{ value: 'admin', label: 'Admin' }
]
}
];
onSubmit(user: User): void {
this.save.emit(user);
}
onFormChange(changes: Partial<User>): void {
console.log('Form changed:', changes);
}
}
📋 Form Validation and Reactive Forms
Advanced patterns for building robust, type-safe form validation systems with sophisticated error handling and user experience optimization.
💡 Type-Safe Form Validation Framework
// Advanced validation result types
interface ValidationResult {
valid: boolean;
errors: ValidationError[];
warnings?: ValidationWarning[];
}
interface ValidationError {
field: string;
code: string;
message: string;
severity: 'error' | 'warning';
context?: any;
}
interface ValidationWarning extends Omit<ValidationError, 'severity'> {
severity: 'warning';
}
// Generic form state management
interface FormState<T> {
value: T;
pristine: boolean;
dirty: boolean;
touched: boolean;
valid: boolean;
pending: boolean;
errors: Record<keyof T, ValidationError[]>;
warnings: Record<keyof T, ValidationWarning[]>;
}
// Advanced validator types
type SyncValidator<T> = (value: T) => ValidationError[] | null;
type AsyncValidator<T> = (value: T) => Observable<ValidationError[] | null>;
type ConditionalValidator<T, K extends keyof T> = (
value: T[K],
formValue: T
) => ValidationError[] | null;
// Validation rule builder
class ValidationRuleBuilder<T, K extends keyof T = keyof T> {
private rules: Array<SyncValidator<T[K]> | AsyncValidator<T[K]>> = [];
private conditionalRules: Array<ConditionalValidator<T, K>> = [];
required(message = 'This field is required'): this {
this.rules.push((value: T[K]) => {
if (value === null || value === undefined || value === '') {
return [{ field: '', code: 'required', message, severity: 'error' as const }];
}
return null;
});
return this;
}
minLength(min: number, message?: string): this {
this.rules.push((value: T[K]) => {
if (typeof value === 'string' && value.length < min) {
return [{
field: '',
code: 'minLength',
message: message || `Minimum length is ${min} characters`,
severity: 'error' as const,
context: { min, actual: value.length }
}];
}
return null;
});
return this;
}
maxLength(max: number, message?: string): this {
this.rules.push((value: T[K]) => {
if (typeof value === 'string' && value.length > max) {
return [{
field: '',
code: 'maxLength',
message: message || `Maximum length is ${max} characters`,
severity: 'error' as const,
context: { max, actual: value.length }
}];
}
return null;
});
return this;
}
pattern(regex: RegExp, message?: string): this {
this.rules.push((value: T[K]) => {
if (typeof value === 'string' && !regex.test(value)) {
return [{
field: '',
code: 'pattern',
message: message || 'Invalid format',
severity: 'error' as const,
context: { pattern: regex.source }
}];
}
return null;
});
return this;
}
email(message = 'Please enter a valid email address'): this {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return this.pattern(emailRegex, message);
}
custom(validator: SyncValidator<T[K]>): this {
this.rules.push(validator);
return this;
}
asyncCustom(validator: AsyncValidator<T[K]>): this {
this.rules.push(validator);
return this;
}
when(condition: (formValue: T) => boolean, validator: ConditionalValidator<T, K>): this {
this.conditionalRules.push((value, formValue) => {
return condition(formValue) ? validator(value, formValue) : null;
});
return this;
}
// Unique validation with backend check
unique(
checkFn: (value: T[K]) => Observable<boolean>,
message = 'This value already exists'
): this {
this.asyncCustom((value: T[K]) => {
if (!value) return of(null);
return checkFn(value).pipe(
map(exists => exists ? [{
field: '',
code: 'unique',
message,
severity: 'error' as const
}] : null),
catchError(() => of(null))
);
});
return this;
}
build(): {
syncValidators: SyncValidator<T[K]>[];
asyncValidators: AsyncValidator<T[K]>[];
conditionalValidators: ConditionalValidator<T, K>[];
} {
return {
syncValidators: this.rules.filter(rule =>
typeof rule === 'function' && rule.constructor.name !== 'AsyncFunction'
) as SyncValidator<T[K]>[],
asyncValidators: this.rules.filter(rule =>
typeof rule === 'function' && rule.constructor.name === 'AsyncFunction'
) as AsyncValidator<T[K]>[],
conditionalValidators: this.conditionalRules
};
}
}
// Type-safe form builder
class TypedFormBuilder {
static group<T>(
config: {
[K in keyof T]: {
value: T[K];
validators?: ValidationRuleBuilder<T, K>;
disabled?: boolean;
}
}
): TypedFormGroup<T> {
const controls: { [K in keyof T]: TypedFormControl<T[K]> } = {} as any;
for (const [key, fieldConfig] of Object.entries(config) as Array<[keyof T, any]>) {
const validators = fieldConfig.validators?.build();
controls[key] = new TypedFormControl(
fieldConfig.value,
validators?.syncValidators || [],
validators?.asyncValidators || [],
fieldConfig.disabled
);
}
return new TypedFormGroup(controls);
}
}
// Enhanced form control with type safety
class TypedFormControl<T> {
private _value: T;
private _pristine = true;
private _touched = false;
private _pending = false;
private _errors: ValidationError[] = [];
private _warnings: ValidationWarning[] = [];
valueChanges = new BehaviorSubject<T>(this._value);
statusChanges = new BehaviorSubject<'valid' | 'invalid' | 'pending'>('valid');
constructor(
initialValue: T,
private syncValidators: SyncValidator<T>[] = [],
private asyncValidators: AsyncValidator<T>[] = [],
private disabled = false
) {
this._value = initialValue;
this.validateSync();
}
get value(): T { return this._value; }
get pristine(): boolean { return this._pristine; }
get dirty(): boolean { return !this._pristine; }
get touched(): boolean { return this._touched; }
get pending(): boolean { return this._pending; }
get valid(): boolean { return this._errors.length === 0; }
get invalid(): boolean { return !this.valid; }
get errors(): ValidationError[] { return [...this._errors]; }
get warnings(): ValidationWarning[] { return [...this._warnings]; }
setValue(value: T, options: { emitEvent?: boolean } = {}): void {
this._value = value;
this._pristine = false;
this.validateSync();
this.validateAsync();
if (options.emitEvent !== false) {
this.valueChanges.next(this._value);
this.statusChanges.next(this.valid ? 'valid' : 'invalid');
}
}
markAsTouched(): void {
this._touched = true;
}
markAsPristine(): void {
this._pristine = true;
}
private validateSync(): void {
this._errors = [];
for (const validator of this.syncValidators) {
const errors = validator(this._value);
if (errors) {
this._errors.push(...errors);
}
}
}
private validateAsync(): void {
if (this.asyncValidators.length === 0) return;
this._pending = true;
this.statusChanges.next('pending');
const asyncValidations = this.asyncValidators.map(validator =>
validator(this._value).pipe(catchError(() => of(null)))
);
forkJoin(asyncValidations).subscribe(results => {
const asyncErrors = results
.filter(result => result !== null)
.flat() as ValidationError[];
this._errors = this._errors.filter(error =>
!this.asyncValidators.some(validator =>
validator.name === error.code
)
);
this._errors.push(...asyncErrors);
this._pending = false;
this.statusChanges.next(this.valid ? 'valid' : 'invalid');
});
}
}
// Enhanced form group with type safety
class TypedFormGroup<T> {
private _pristine = true;
private _touched = false;
private _pending = false;
valueChanges: Observable<T>;
statusChanges: Observable<'valid' | 'invalid' | 'pending'>;
constructor(private controls: { [K in keyof T]: TypedFormControl<T[K]> }) {
this.valueChanges = combineLatest(
Object.entries(this.controls).map(([key, control]) =>
(control as TypedFormControl<any>).valueChanges.pipe(
map(value => ({ [key]: value }))
)
)
).pipe(
map(values => Object.assign({}, ...values) as T)
);
this.statusChanges = combineLatest(
Object.values(this.controls).map(control =>
(control as TypedFormControl<any>).statusChanges
)
).pipe(
map(statuses => {
if (statuses.some(status => status === 'pending')) return 'pending';
if (statuses.some(status => status === 'invalid')) return 'invalid';
return 'valid';
})
);
}
get value(): T {
const result = {} as T;
for (const [key, control] of Object.entries(this.controls)) {
(result as any)[key] = (control as TypedFormControl<any>).value;
}
return result;
}
get valid(): boolean {
return Object.values(this.controls).every(control =>
(control as TypedFormControl<any>).valid
);
}
get invalid(): boolean {
return !this.valid;
}
get pristine(): boolean {
return Object.values(this.controls).every(control =>
(control as TypedFormControl<any>).pristine
);
}
get dirty(): boolean {
return !this.pristine;
}
get touched(): boolean {
return Object.values(this.controls).some(control =>
(control as TypedFormControl<any>).touched
);
}
get(field: keyof T): TypedFormControl<T[keyof T]> {
return this.controls[field];
}
setValue(value: Partial<T>): void {
for (const [key, val] of Object.entries(value)) {
if (this.controls[key as keyof T]) {
(this.controls[key as keyof T] as TypedFormControl<any>).setValue(val);
}
}
}
patchValue(value: Partial<T>): void {
this.setValue(value);
}
markAllAsTouched(): void {
Object.values(this.controls).forEach(control =>
(control as TypedFormControl<any>).markAsTouched()
);
}
reset(): void {
Object.values(this.controls).forEach(control =>
(control as TypedFormControl<any>).markAsPristine()
);
}
getErrors(): Record<keyof T, ValidationError[]> {
const errors = {} as Record<keyof T, ValidationError[]>;
for (const [key, control] of Object.entries(this.controls)) {
errors[key as keyof T] = (control as TypedFormControl<any>).errors;
}
return errors;
}
}
// Real-world usage in Angular component
interface RegistrationForm {
email: string;
password: string;
confirmPassword: string;
firstName: string;
lastName: string;
username: string;
acceptTerms: boolean;
}
@Component({
selector: 'app-registration-form',
template: `
<form [formGroup]="angularFormGroup" (ngSubmit)="onSubmit()">
<div class="form-field">
<label for="email">Email</label>
<input
id="email"
type="email"
formControlName="email"
[class.error]="hasErrors('email')"
(blur)="markFieldAsTouched('email')">
<div *ngIf="hasErrors('email')" class="error-messages">
<span *ngFor="let error of getFieldErrors('email')">
{{ error.message }}
</span>
</div>
</div>
<div class="form-field">
<label for="password">Password</label>
<input
id="password"
type="password"
formControlName="password"
[class.error]="hasErrors('password')"
(blur)="markFieldAsTouched('password')">
<div *ngIf="hasErrors('password')" class="error-messages">
<span *ngFor="let error of getFieldErrors('password')">
{{ error.message }}
</span>
</div>
</div>
<div class="form-field">
<label for="confirmPassword">Confirm Password</label>
<input
id="confirmPassword"
type="password"
formControlName="confirmPassword"
[class.error]="hasErrors('confirmPassword')"
(blur)="markFieldAsTouched('confirmPassword')">
<div *ngIf="hasErrors('confirmPassword')" class="error-messages">
<span *ngFor="let error of getFieldErrors('confirmPassword')">
{{ error.message }}
</span>
</div>
</div>
<button
type="submit"
[disabled]="!typedForm.valid || isSubmitting"
[class.loading]="isSubmitting">
{{ isSubmitting ? 'Creating Account...' : 'Create Account' }}
</button>
</form>
`,
styleUrls: ['./registration-form.component.scss']
})
export class RegistrationFormComponent implements OnInit {
typedForm!: TypedFormGroup<RegistrationForm>;
angularFormGroup!: FormGroup;
isSubmitting = false;
constructor(
private fb: FormBuilder,
private userService: UserService,
private authService: AuthService
) {}
ngOnInit(): void {
this.createTypedForm();
this.createAngularForm();
}
private createTypedForm(): void {
this.typedForm = TypedFormBuilder.group<RegistrationForm>({
email: {
value: '',
validators: new ValidationRuleBuilder<RegistrationForm, 'email'>()
.required()
.email()
.unique(
email => this.userService.checkEmailExists(email),
'This email is already registered'
)
},
password: {
value: '',
validators: new ValidationRuleBuilder<RegistrationForm, 'password'>()
.required()
.minLength(8, 'Password must be at least 8 characters')
.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/,
'Password must contain uppercase, lowercase, number and special character'
)
},
confirmPassword: {
value: '',
validators: new ValidationRuleBuilder<RegistrationForm, 'confirmPassword'>()
.required()
.when(
form => !!form.password,
(confirmPassword, form) => {
if (confirmPassword !== form.password) {
return [{
field: 'confirmPassword',
code: 'passwordMismatch',
message: 'Passwords do not match',
severity: 'error'
}];
}
return null;
}
)
},
firstName: {
value: '',
validators: new ValidationRuleBuilder<RegistrationForm, 'firstName'>()
.required()
.minLength(2)
.maxLength(50)
},
lastName: {
value: '',
validators: new ValidationRuleBuilder<RegistrationForm, 'lastName'>()
.required()
.minLength(2)
.maxLength(50)
},
username: {
value: '',
validators: new ValidationRuleBuilder<RegistrationForm, 'username'>()
.required()
.minLength(3)
.maxLength(20)
.pattern(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores')
.unique(
username => this.userService.checkUsernameExists(username),
'This username is already taken'
)
},
acceptTerms: {
value: false,
validators: new ValidationRuleBuilder<RegistrationForm, 'acceptTerms'>()
.custom(value => {
if (!value) {
return [{
field: 'acceptTerms',
code: 'required',
message: 'You must accept the terms and conditions',
severity: 'error'
}];
}
return null;
})
}
});
}
private createAngularForm(): void {
// Create parallel Angular FormGroup for template integration
this.angularFormGroup = this.fb.group({
email: [''],
password: [''],
confirmPassword: [''],
firstName: [''],
lastName: [''],
username: [''],
acceptTerms: [false]
});
// Sync typed form with Angular form
this.typedForm.valueChanges.subscribe(value => {
this.angularFormGroup.patchValue(value, { emitEvent: false });
});
this.angularFormGroup.valueChanges.subscribe(value => {
this.typedForm.setValue(value);
});
}
hasErrors(field: keyof RegistrationForm): boolean {
const control = this.typedForm.get(field);
return control.invalid && control.touched;
}
getFieldErrors(field: keyof RegistrationForm): ValidationError[] {
return this.typedForm.get(field).errors;
}
markFieldAsTouched(field: keyof RegistrationForm): void {
this.typedForm.get(field).markAsTouched();
}
onSubmit(): void {
if (this.typedForm.valid) {
this.isSubmitting = true;
const formValue = this.typedForm.value;
this.authService.register(formValue).subscribe({
next: (response) => {
console.log('Registration successful:', response);
this.isSubmitting = false;
// Handle success (redirect, show message, etc.)
},
error: (error) => {
console.error('Registration failed:', error);
this.isSubmitting = false;
// Handle error (show message, etc.)
}
});
} else {
this.typedForm.markAllAsTouched();
}
}
}
⚡ Advanced Cross-Field Validation
// Cross-field validation patterns
interface CrossFieldValidator<T> {
validate(form: T): ValidationError[] | null;
fields: Array<keyof T>;
}
class CrossFieldValidationBuilder<T> {
private validators: CrossFieldValidator<T>[] = [];
compareFields<K1 extends keyof T, K2 extends keyof T>(
field1: K1,
field2: K2,
comparison: 'equal' | 'notEqual' | 'greater' | 'greaterOrEqual' | 'less' | 'lessOrEqual',
message?: string
): this {
this.validators.push({
fields: [field1, field2],
validate: (form: T) => {
const value1 = form[field1];
const value2 = form[field2];
let isValid = false;
switch (comparison) {
case 'equal':
isValid = value1 === value2;
break;
case 'notEqual':
isValid = value1 !== value2;
break;
case 'greater':
isValid = value1 > value2;
break;
case 'greaterOrEqual':
isValid = value1 >= value2;
break;
case 'less':
isValid = value1 < value2;
break;
case 'lessOrEqual':
isValid = value1 <= value2;
break;
}
if (!isValid) {
return [{
field: field2 as string,
code: `compare_${comparison}`,
message: message || `${String(field2)} must be ${comparison} to ${String(field1)}`,
severity: 'error'
}];
}
return null;
}
});
return this;
}
dateRange<K1 extends keyof T, K2 extends keyof T>(
startField: K1,
endField: K2,
message = 'End date must be after start date'
): this {
this.validators.push({
fields: [startField, endField],
validate: (form: T) => {
const startDate = new Date(form[startField] as any);
const endDate = new Date(form[endField] as any);
if (startDate >= endDate) {
return [{
field: endField as string,
code: 'dateRange',
message,
severity: 'error'
}];
}
return null;
}
});
return this;
}
conditionalRequired<K1 extends keyof T, K2 extends keyof T>(
dependentField: K1,
requiredField: K2,
condition: (value: T[K1]) => boolean,
message?: string
): this {
this.validators.push({
fields: [dependentField, requiredField],
validate: (form: T) => {
const dependentValue = form[dependentField];
const requiredValue = form[requiredField];
if (condition(dependentValue) && !requiredValue) {
return [{
field: requiredField as string,
code: 'conditionalRequired',
message: message || `${String(requiredField)} is required`,
severity: 'error'
}];
}
return null;
}
});
return this;
}
custom(validator: CrossFieldValidator<T>): this {
this.validators.push(validator);
return this;
}
build(): CrossFieldValidator<T>[] {
return [...this.validators];
}
}
// Enhanced typed form group with cross-field validation
class EnhancedTypedFormGroup<T> extends TypedFormGroup<T> {
private crossFieldValidators: CrossFieldValidator<T>[] = [];
constructor(
controls: { [K in keyof T]: TypedFormControl<T[K]> },
crossFieldValidators: CrossFieldValidator<T>[] = []
) {
super(controls);
this.crossFieldValidators = crossFieldValidators;
// Re-validate cross-field rules when any field changes
this.valueChanges.subscribe(() => {
this.validateCrossFields();
});
}
private validateCrossFields(): void {
const formValue = this.value;
// Clear previous cross-field errors
for (const validator of this.crossFieldValidators) {
for (const field of validator.fields) {
const control = this.get(field);
// Remove cross-field errors (keep field-level errors)
(control as any)._errors = (control as any)._errors.filter(
(error: ValidationError) => !this.isCrossFieldError(error)
);
}
}
// Apply cross-field validation
for (const validator of this.crossFieldValidators) {
const errors = validator.validate(formValue);
if (errors) {
for (const error of errors) {
const control = this.get(error.field as keyof T);
(control as any)._errors.push({
...error,
crossField: true
});
}
}
}
}
private isCrossFieldError(error: ValidationError): boolean {
return (error as any).crossField === true;
}
}
// Real-world example: Event booking form
interface EventBookingForm {
eventType: 'workshop' | 'conference' | 'webinar';
startDate: string;
endDate: string;
attendeeCount: number;
budget: number;
venueType: 'physical' | 'virtual' | 'hybrid';
venueAddress?: string;
cateringRequired: boolean;
cateringBudget?: number;
specialRequirements?: string;
}
@Component({
selector: 'app-event-booking-form',
template: `
<form [formGroup]="angularForm" (ngSubmit)="onSubmit()">
<!-- Event type selection -->
<div class="form-field">
<label for="eventType">Event Type</label>
<select id="eventType" formControlName="eventType">
<option value="workshop">Workshop</option>
<option value="conference">Conference</option>
<option value="webinar">Webinar</option>
</select>
</div>
<!-- Date range -->
<div class="form-row">
<div class="form-field">
<label for="startDate">Start Date</label>
<input
id="startDate"
type="date"
formControlName="startDate"
[class.error]="hasErrors('startDate')">
</div>
<div class="form-field">
<label for="endDate">End Date</label>
<input
id="endDate"
type="date"
formControlName="endDate"
[class.error]="hasErrors('endDate')">
</div>
</div>
<!-- Conditional venue address -->
<div class="form-field" *ngIf="showVenueAddress()">
<label for="venueAddress">Venue Address</label>
<textarea
id="venueAddress"
formControlName="venueAddress"
[class.error]="hasErrors('venueAddress')">
</textarea>
</div>
<!-- Conditional catering budget -->
<div class="form-field" *ngIf="showCateringBudget()">
<label for="cateringBudget">Catering Budget</label>
<input
id="cateringBudget"
type="number"
formControlName="cateringBudget"
[class.error]="hasErrors('cateringBudget')">
</div>
<button type="submit" [disabled]="!typedForm.valid">
Book Event
</button>
</form>
`
})
export class EventBookingFormComponent implements OnInit {
typedForm!: EnhancedTypedFormGroup<EventBookingForm>;
angularForm!: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.createTypedForm();
this.createAngularForm();
}
private createTypedForm(): void {
const controls = {
eventType: new TypedFormControl<EventBookingForm['eventType']>('workshop'),
startDate: new TypedFormControl(''),
endDate: new TypedFormControl(''),
attendeeCount: new TypedFormControl(1),
budget: new TypedFormControl(0),
venueType: new TypedFormControl<EventBookingForm['venueType']>('physical'),
venueAddress: new TypedFormControl(''),
cateringRequired: new TypedFormControl(false),
cateringBudget: new TypedFormControl(0),
specialRequirements: new TypedFormControl('')
};
const crossFieldValidators = new CrossFieldValidationBuilder<EventBookingForm>()
.dateRange('startDate', 'endDate')
.conditionalRequired(
'venueType',
'venueAddress',
venueType => venueType === 'physical' || venueType === 'hybrid',
'Venue address is required for physical/hybrid events'
)
.conditionalRequired(
'cateringRequired',
'cateringBudget',
required => required,
'Catering budget is required when catering is selected'
)
.custom({
fields: ['cateringBudget', 'budget'],
validate: (form) => {
if (form.cateringRequired && form.cateringBudget! > form.budget * 0.3) {
return [{
field: 'cateringBudget',
code: 'budgetExceeded',
message: 'Catering budget cannot exceed 30% of total budget',
severity: 'error'
}];
}
return null;
}
})
.build();
this.typedForm = new EnhancedTypedFormGroup(controls, crossFieldValidators);
}
private createAngularForm(): void {
this.angularForm = this.fb.group({
eventType: ['workshop'],
startDate: [''],
endDate: [''],
attendeeCount: [1],
budget: [0],
venueType: ['physical'],
venueAddress: [''],
cateringRequired: [false],
cateringBudget: [0],
specialRequirements: ['']
});
// Sync forms
this.syncForms();
}
private syncForms(): void {
this.typedForm.valueChanges.subscribe(value => {
this.angularForm.patchValue(value, { emitEvent: false });
});
this.angularForm.valueChanges.subscribe(value => {
this.typedForm.setValue(value);
});
}
hasErrors(field: keyof EventBookingForm): boolean {
const control = this.typedForm.get(field);
return control.invalid && control.touched;
}
showVenueAddress(): boolean {
const venueType = this.typedForm.get('venueType').value;
return venueType === 'physical' || venueType === 'hybrid';
}
showCateringBudget(): boolean {
return this.typedForm.get('cateringRequired').value;
}
onSubmit(): void {
if (this.typedForm.valid) {
const formValue = this.typedForm.value;
console.log('Event booking:', formValue);
// Submit form
} else {
this.typedForm.markAllAsTouched();
}
}
}
🏢 COMPANY-TIER CHALLENGES
🎯 Interview Challenge Categories
FAANG+ Level - Meta, Google, Amazon, Netflix, Apple, Microsoft - Advanced type system manipulation - Complex generic constraints - Performance-critical type operations - System design with TypeScript
Senior Level - Unicorns, Series B+ Startups, Enterprise - Practical type safety implementations - Architecture pattern applications - Advanced Angular integration - Code maintainability focus
Mid-Level - Growing companies, Series A startups - Foundational pattern mastery - Common use case implementations - Basic type safety concepts - Real-world problem solving
🚀 FAANG+ Level Challenges
Challenge 1: Type-Safe Event System
Companies: Meta, Google - System Design Focus
/**
* CHALLENGE: Design a type-safe event system that:
* 1. Ensures event payload types match event names
* 2. Provides compile-time validation for event subscriptions
* 3. Supports event middleware with type safety
* 4. Handles unsubscription with proper cleanup
* 5. Scales to thousands of event types
*/
// Your task: Implement the missing types and classes
interface EventMap {
'user:login': { userId: string; timestamp: Date };
'user:logout': { userId: string; reason: string };
'order:created': { orderId: string; amount: number; items: string[] };
'order:cancelled': { orderId: string; reason: string };
'system:error': { code: number; message: string; stack?: string };
// ... potentially thousands more
}
// SOLUTION:
type EventHandler<T> = (payload: T) => void | Promise<void>;
type Middleware<T> = (payload: T, next: () => void) => void | Promise<void>;
type Unsubscribe = () => void;
interface TypeSafeEventEmitter {
on<K extends keyof EventMap>(
event: K,
handler: EventHandler<EventMap[K]>
): Unsubscribe;
emit<K extends keyof EventMap>(
event: K,
payload: EventMap[K]
): Promise<void>;
use<K extends keyof EventMap>(
event: K,
middleware: Middleware<EventMap[K]>
): void;
}
class EventEmitter implements TypeSafeEventEmitter {
private handlers = new Map<string, Set<EventHandler<any>>>();
private middlewares = new Map<string, Middleware<any>[]>();
on<K extends keyof EventMap>(
event: K,
handler: EventHandler<EventMap[K]>
): Unsubscribe {
if (!this.handlers.has(event as string)) {
this.handlers.set(event as string, new Set());
}
const handlersSet = this.handlers.get(event as string)!;
handlersSet.add(handler);
return () => {
handlersSet.delete(handler);
if (handlersSet.size === 0) {
this.handlers.delete(event as string);
}
};
}
async emit<K extends keyof EventMap>(
event: K,
payload: EventMap[K]
): Promise<void> {
const middlewares = this.middlewares.get(event as string) || [];
const handlers = this.handlers.get(event as string);
if (!handlers) return;
// Execute middlewares
let middlewareIndex = 0;
const runMiddleware = async (): Promise<void> => {
if (middlewareIndex < middlewares.length) {
const middleware = middlewares[middlewareIndex++];
await middleware(payload, runMiddleware);
} else {
// Execute handlers after middleware chain
await Promise.all(
Array.from(handlers).map(handler => handler(payload))
);
}
};
await runMiddleware();
}
use<K extends keyof EventMap>(
event: K,
middleware: Middleware<EventMap[K]>
): void {
if (!this.middlewares.has(event as string)) {
this.middlewares.set(event as string, []);
}
this.middlewares.get(event as string)!.push(middleware);
}
}
// Advanced Usage in Angular
@Injectable({ providedIn: 'root' })
export class AnalyticsService {
constructor(private eventEmitter: EventEmitter) {
this.setupAnalyticsMiddleware();
}
private setupAnalyticsMiddleware(): void {
// Type-safe middleware for user events
this.eventEmitter.use('user:login', async (payload, next) => {
console.log('Analytics: User login', payload.userId);
await this.trackEvent('user_login', { user_id: payload.userId });
next();
});
this.eventEmitter.use('order:created', async (payload, next) => {
console.log('Analytics: Order created', payload.orderId);
await this.trackEvent('order_created', {
order_id: payload.orderId,
amount: payload.amount,
item_count: payload.items.length
});
next();
});
}
private async trackEvent(event: string, data: any): Promise<void> {
// Send to analytics service
}
}
Challenge 2: Advanced State Management Types
Companies: Netflix, Amazon - Complex Generic Constraints
/**
* CHALLENGE: Create a type-safe state management system that:
* 1. Infers action types from action creators
* 2. Ensures reducer compatibility with state shape
* 3. Provides type-safe selectors
* 4. Supports nested state updates
* 5. Enables time-travel debugging with type safety
*/
// Your task: Complete the missing type definitions
interface AppState {
user: {
profile: { name: string; email: string };
preferences: { theme: 'light' | 'dark'; notifications: boolean };
};
orders: {
items: Array<{ id: string; status: 'pending' | 'completed' | 'cancelled' }>;
pagination: { page: number; total: number };
};
ui: {
loading: Record<string, boolean>;
errors: Record<string, string | null>;
};
}
// SOLUTION:
type Action<T extends string = string, P = any> = {
type: T;
payload: P;
};
type ActionCreator<T extends string, P> = (payload: P) => Action<T, P>;
type ActionCreators = {
[K: string]: ActionCreator<any, any>;
};
type InferActionTypes<T extends ActionCreators> = {
[K in keyof T]: T[K] extends ActionCreator<infer Type, infer Payload>
? Action<Type, Payload>
: never;
}[keyof T];
type Reducer<S, A extends Action> = (state: S, action: A) => S;
type Selector<S, R> = (state: S) => R;
// Path-based state access
type PathKeys<T, D extends number = 10> = [D] extends [never]
? never
: T extends object
? {
[K in keyof T]-?: K extends string | number
? `${K}` | Join<K, PathKeys<T[K], Prev[D]>>
: never;
}[keyof T]
: '';
type Join<K, P> = K extends string | number
? P extends string | number
? `${K}${'' extends P ? '' : '.'}${P}`
: never
: never;
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...0[]];
type GetByPath<T, P extends string> = P extends `${infer K}.${infer Rest}`
? K extends keyof T
? GetByPath<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
// Advanced store implementation
class TypeSafeStore<S, A extends Action> {
private state: S;
private reducer: Reducer<S, A>;
private listeners: Array<() => void> = [];
private history: S[] = [];
constructor(initialState: S, reducer: Reducer<S, A>) {
this.state = initialState;
this.reducer = reducer;
this.history.push(initialState);
}
getState(): S {
return this.state;
}
dispatch(action: A): void {
const newState = this.reducer(this.state, action);
this.state = newState;
this.history.push(newState);
// Keep history limited
if (this.history.length > 50) {
this.history.shift();
}
this.listeners.forEach(listener => listener());
}
select<P extends PathKeys<S>, R = GetByPath<S, P>>(
path: P
): R {
return this.getValueByPath(this.state, path) as R;
}
subscribe(listener: () => void): () => void {
this.listeners.push(listener);
return () => {
const index = this.listeners.indexOf(listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
};
}
// Time-travel debugging
goToState(index: number): void {
if (index >= 0 && index < this.history.length) {
this.state = this.history[index];
this.listeners.forEach(listener => listener());
}
}
getHistory(): S[] {
return [...this.history];
}
private getValueByPath(obj: any, path: string): any {
return path.split('.').reduce((current, key) => current?.[key], obj);
}
}
// Action creators with type inference
const actionCreators = {
updateUserProfile: (profile: AppState['user']['profile']) => ({
type: 'UPDATE_USER_PROFILE' as const,
payload: profile
}),
toggleTheme: () => ({
type: 'TOGGLE_THEME' as const,
payload: undefined
}),
addOrder: (order: AppState['orders']['items'][0]) => ({
type: 'ADD_ORDER' as const,
payload: order
}),
setLoading: (key: string, loading: boolean) => ({
type: 'SET_LOADING' as const,
payload: { key, loading }
})
};
type AppActions = InferActionTypes<typeof actionCreators>;
// Type-safe reducer
const appReducer: Reducer<AppState, AppActions> = (state, action) => {
switch (action.type) {
case 'UPDATE_USER_PROFILE':
return {
...state,
user: {
...state.user,
profile: { ...state.user.profile, ...action.payload }
}
};
case 'TOGGLE_THEME':
return {
...state,
user: {
...state.user,
preferences: {
...state.user.preferences,
theme: state.user.preferences.theme === 'light' ? 'dark' : 'light'
}
}
};
case 'ADD_ORDER':
return {
...state,
orders: {
...state.orders,
items: [...state.orders.items, action.payload]
}
};
case 'SET_LOADING':
return {
...state,
ui: {
...state.ui,
loading: {
...state.ui.loading,
[action.payload.key]: action.payload.loading
}
}
};
default:
return state;
}
};
// Angular integration
@Injectable({ providedIn: 'root' })
export class StoreService {
private store: TypeSafeStore<AppState, AppActions>;
constructor() {
const initialState: AppState = {
user: {
profile: { name: '', email: '' },
preferences: { theme: 'light', notifications: true }
},
orders: {
items: [],
pagination: { page: 1, total: 0 }
},
ui: {
loading: {},
errors: {}
}
};
this.store = new TypeSafeStore(initialState, appReducer);
}
dispatch(action: AppActions): void {
this.store.dispatch(action);
}
// Type-safe selectors
selectUserName(): string {
return this.store.select('user.profile.name');
}
selectTheme(): 'light' | 'dark' {
return this.store.select('user.preferences.theme');
}
selectOrderCount(): number {
return this.store.select('orders.items').length;
}
selectIsLoading(key: string): boolean {
return this.store.select('ui.loading')[key] || false;
}
subscribe(listener: () => void): () => void {
return this.store.subscribe(listener);
}
// Debug methods
getHistory(): AppState[] {
return this.store.getHistory();
}
timeTravel(index: number): void {
this.store.goToState(index);
}
}
Challenge 3: Performance-Critical Type Operations
Companies: Apple, Microsoft - Optimization Focus
/**
* CHALLENGE: Optimize TypeScript compilation and runtime performance:
* 1. Create efficient type operations for large datasets
* 2. Minimize type instantiation overhead
* 3. Implement compile-time optimizations
* 4. Build memory-efficient type structures
* 5. Handle recursive types without stack overflow
*/
// Your task: Optimize these patterns for performance
// SOLUTION: Efficient type operations
type OptimizedPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type OptimizedOmit<T, K extends keyof T> = OptimizedPick<T, Exclude<keyof T, K>>;
// Tail-recursive type operations to prevent stack overflow
type TailRecursiveLength<T extends readonly any[], Acc extends any[] = []> =
T extends readonly [any, ...infer Rest]
? Acc['length'] extends 1000 // Prevent infinite recursion
? never
: TailRecursiveLength<Rest, [...Acc, any]>
: Acc['length'];
// Efficient union operations
type FastUnion<T, U> = T | U;
type FastIntersection<T, U> = T & U;
// Lazy evaluation patterns
type LazyEvaluate<T> = T extends (...args: any[]) => infer R ? R : T;
// Cache-friendly type operations
interface TypeCache {
[key: string]: any;
}
type CachedType<K extends string, T> = K extends keyof TypeCache
? TypeCache[K]
: T;
// Memory-efficient deep operations
type ShallowClone<T> = { [K in keyof T]: T[K] };
type DeepCloneOptimized<T, MaxDepth extends number = 5> =
MaxDepth extends 0
? T
: T extends object
? {
[K in keyof T]: T[K] extends object
? DeepCloneOptimized<T[K], Subtract<MaxDepth, 1>>
: T[K]
}
: T;
// Efficient data processing pipeline
class OptimizedDataProcessor<T> {
private cache = new Map<string, any>();
private typeCache = new WeakMap<object, any>();
process<R>(
data: T[],
operations: Array<(item: T) => R>
): R[] {
const cacheKey = this.getCacheKey(operations);
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
// Use efficient processing with minimal allocations
const result = data.flatMap(item =>
operations.map(op => this.processWithTypeCache(item, op))
);
this.cache.set(cacheKey, result);
return result;
}
private processWithTypeCache<R>(item: T, operation: (item: T) => R): R {
if (this.typeCache.has(operation)) {
const cachedOp = this.typeCache.get(operation);
return cachedOp(item);
}
const result = operation(item);
this.typeCache.set(operation, operation);
return result;
}
private getCacheKey(operations: any[]): string {
return operations.map(op => op.toString()).join('|');
}
clearCache(): void {
this.cache.clear();
this.typeCache = new WeakMap();
}
}
// High-performance Angular service
@Injectable({ providedIn: 'root' })
export class HighPerformanceDataService<T> {
private processor = new OptimizedDataProcessor<T>();
private memoizedResults = new Map<string, any>();
// Optimized bulk operations
@Memoize()
processBulkData(
data: T[],
transformations: Array<(item: T) => any>
): any[] {
// Use efficient chunking for large datasets
const chunkSize = 1000;
const chunks: T[][] = [];
for (let i = 0; i < data.length; i += chunkSize) {
chunks.push(data.slice(i, i + chunkSize));
}
// Process chunks in parallel with Web Workers if available
return chunks.flatMap(chunk =>
this.processor.process(chunk, transformations)
);
}
// Memory-efficient streaming operations
*streamProcess<R>(
data: AsyncIterable<T>,
transform: (item: T) => R
): AsyncGenerator<R, void, unknown> {
for await (const item of data) {
yield transform(item);
}
}
// Optimized query operations with indexing
createIndex<K extends keyof T>(
data: T[],
keySelector: K
): Map<T[K], T[]> {
const index = new Map<T[K], T[]>();
for (const item of data) {
const key = item[keySelector];
if (!index.has(key)) {
index.set(key, []);
}
index.get(key)!.push(item);
}
return index;
}
// Efficient batch updates
batchUpdate<U>(
data: T[],
updates: Partial<T>[],
keySelector: (item: T) => string
): T[] {
const updateMap = new Map<string, Partial<T>>();
for (const update of updates) {
const key = keySelector(update as T);
updateMap.set(key, update);
}
return data.map(item => {
const key = keySelector(item);
const update = updateMap.get(key);
return update ? { ...item, ...update } : item;
});
}
}
// Memoization decorator for performance
function Memoize() {
return function (
target: any,
propertyName: string,
descriptor: PropertyDescriptor
) {
const method = descriptor.value;
const cache = new Map();
descriptor.value = function (...args: any[]) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = method.apply(this, args);
cache.set(key, result);
return result;
};
};
}
💼 Senior Level Challenges
Challenge 4: Practical Type Safety Implementation
Companies: Stripe, Shopify, Atlassian
/**
* CHALLENGE: Build a type-safe API client that:
* 1. Validates request/response schemas at compile time
* 2. Provides intelligent auto-completion
* 3. Handles error scenarios gracefully
* 4. Supports API versioning
* 5. Enables request/response interceptors
*/
// Define API schema
interface APISchema {
'GET /users': {
query?: { page?: number; limit?: number; search?: string };
response: { users: User[]; total: number; page: number };
};
'POST /users': {
body: Omit<User, 'id' | 'createdAt'>;
response: User;
};
'GET /users/:id': {
params: { id: string };
response: User;
};
'PUT /users/:id': {
params: { id: string };
body: Partial<Omit<User, 'id' | 'createdAt'>>;
response: User;
};
'DELETE /users/:id': {
params: { id: string };
response: { success: boolean };
};
}
// SOLUTION:
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
type APIEndpoint = keyof APISchema;
type ExtractMethod<T extends APIEndpoint> =
T extends `${infer M} ${string}` ? M : never;
type ExtractPath<T extends APIEndpoint> =
T extends `${string} ${infer P}` ? P : never;
type EndpointConfig<T extends APIEndpoint> = APISchema[T];
type RequestConfig<T extends APIEndpoint> = {
params?: EndpointConfig<T> extends { params: infer P } ? P : never;
query?: EndpointConfig<T> extends { query: infer Q } ? Q : never;
body?: EndpointConfig<T> extends { body: infer B } ? B : never;
headers?: Record<string, string>;
};
type ResponseType<T extends APIEndpoint> =
EndpointConfig<T> extends { response: infer R } ? R : never;
// Type-safe API client
class TypeSafeAPIClient {
private baseURL: string;
private defaultHeaders: Record<string, string> = {};
private interceptors: {
request: Array<(config: any) => any>;
response: Array<(response: any) => any>;
} = { request: [], response: [] };
constructor(baseURL: string) {
this.baseURL = baseURL;
}
async request<T extends APIEndpoint>(
endpoint: T,
config: RequestConfig<T> = {} as RequestConfig<T>
): Promise<ResponseType<T>> {
const method = this.extractMethod(endpoint);
const path = this.buildPath(endpoint, config.params);
const url = this.buildURL(path, config.query);
let requestConfig: RequestInit = {
method,
headers: {
'Content-Type': 'application/json',
...this.defaultHeaders,
...config.headers
}
};
if (config.body && method !== 'GET') {
requestConfig.body = JSON.stringify(config.body);
}
// Apply request interceptors
for (const interceptor of this.interceptors.request) {
requestConfig = interceptor(requestConfig);
}
try {
const response = await fetch(url, requestConfig);
if (!response.ok) {
throw new APIError(response.status, response.statusText);
}
let data = await response.json();
// Apply response interceptors
for (const interceptor of this.interceptors.response) {
data = interceptor(data);
}
return data;
} catch (error) {
if (error instanceof APIError) {
throw error;
}
throw new APIError(0, 'Network error', error);
}
}
// Type-safe convenience methods
async get<T extends Extract<APIEndpoint, `GET ${string}`>>(
endpoint: T,
config?: Omit<RequestConfig<T>, 'body'>
): Promise<ResponseType<T>> {
return this.request(endpoint, config as RequestConfig<T>);
}
async post<T extends Extract<APIEndpoint, `POST ${string}`>>(
endpoint: T,
config: RequestConfig<T>
): Promise<ResponseType<T>> {
return this.request(endpoint, config);
}
async put<T extends Extract<APIEndpoint, `PUT ${string}`>>(
endpoint: T,
config: RequestConfig<T>
): Promise<ResponseType<T>> {
return this.request(endpoint, config);
}
async delete<T extends Extract<APIEndpoint, `DELETE ${string}`>>(
endpoint: T,
config?: Omit<RequestConfig<T>, 'body'>
): Promise<ResponseType<T>> {
return this.request(endpoint, config as RequestConfig<T>);
}
// Interceptor management
addRequestInterceptor(interceptor: (config: any) => any): void {
this.interceptors.request.push(interceptor);
}
addResponseInterceptor(interceptor: (response: any) => any): void {
this.interceptors.response.push(interceptor);
}
setDefaultHeaders(headers: Record<string, string>): void {
this.defaultHeaders = { ...this.defaultHeaders, ...headers };
}
private extractMethod(endpoint: APIEndpoint): HTTPMethod {
return endpoint.split(' ')[0] as HTTPMethod;
}
private buildPath(endpoint: APIEndpoint, params?: any): string {
let path = endpoint.split(' ')[1];
if (params) {
for (const [key, value] of Object.entries(params)) {
path = path.replace(`:${key}`, String(value));
}
}
return path;
}
private buildURL(path: string, query?: any): string {
const url = new URL(path, this.baseURL);
if (query) {
for (const [key, value] of Object.entries(query)) {
if (value !== undefined) {
url.searchParams.append(key, String(value));
}
}
}
return url.toString();
}
}
class APIError extends Error {
constructor(
public status: number,
public statusText: string,
public originalError?: any
) {
super(`API Error ${status}: ${statusText}`);
this.name = 'APIError';
}
}
// Angular service implementation
@Injectable({ providedIn: 'root' })
export class UserAPIService {
private client: TypeSafeAPIClient;
constructor(@Inject('API_BASE_URL') baseURL: string) {
this.client = new TypeSafeAPIClient(baseURL);
this.setupInterceptors();
}
private setupInterceptors(): void {
// Add authentication
this.client.addRequestInterceptor((config) => {
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Add error handling
this.client.addResponseInterceptor((response) => {
// Log analytics, transform data, etc.
return response;
});
}
// Type-safe API methods with full intellisense
async getUsers(options: {
page?: number;
limit?: number;
search?: string;
} = {}): Promise<{ users: User[]; total: number; page: number }> {
return this.client.get('GET /users', { query: options });
}
async createUser(
userData: Omit<User, 'id' | 'createdAt'>
): Promise<User> {
return this.client.post('POST /users', { body: userData });
}
async getUser(id: string): Promise<User> {
return this.client.get('GET /users/:id', { params: { id } });
}
async updateUser(
id: string,
updates: Partial<Omit<User, 'id' | 'createdAt'>>
): Promise<User> {
return this.client.put('PUT /users/:id', {
params: { id },
body: updates
});
}
async deleteUser(id: string): Promise<{ success: boolean }> {
return this.client.delete('DELETE /users/:id', { params: { id } });
}
}
// Usage in component with full type safety
@Component({
selector: 'app-user-management',
template: '...'
})
export class UserManagementComponent implements OnInit {
users: User[] = [];
loading = false;
error: string | null = null;
constructor(private userAPI: UserAPIService) {}
async ngOnInit(): Promise<void> {
await this.loadUsers();
}
async loadUsers(): Promise<void> {
try {
this.loading = true;
this.error = null;
// Full type safety and intellisense
const response = await this.userAPI.getUsers({
page: 1,
limit: 20,
search: ''
});
this.users = response.users;
} catch (error) {
if (error instanceof APIError) {
this.error = `Failed to load users: ${error.message}`;
} else {
this.error = 'An unexpected error occurred';
}
} finally {
this.loading = false;
}
}
async createUser(userData: Omit<User, 'id' | 'createdAt'>): Promise<void> {
try {
const newUser = await this.userAPI.createUser(userData);
this.users.push(newUser);
} catch (error) {
console.error('Failed to create user:', error);
}
}
}
Challenge 5: Architecture Pattern Implementation
Companies: Uber, Airbnb, PayPal
/**
* CHALLENGE: Implement a modular architecture with:
* 1. Type-safe module federation
* 2. Dependency injection with constraints
* 3. Plugin system with type validation
* 4. Configuration management
* 5. Cross-module communication
*/
// Module system definition
interface ModuleMetadata {
name: string;
version: string;
dependencies: string[];
exports: Record<string, any>;
config?: Record<string, any>;
}
interface ModuleContext<T = any> {
config: T;
dependencies: Record<string, any>;
emit: <K extends string>(event: K, payload: any) => void;
subscribe: <K extends string>(event: K, handler: (payload: any) => void) => () => void;
}
// SOLUTION:
abstract class BaseModule<TConfig = any> {
abstract readonly metadata: ModuleMetadata;
protected context!: ModuleContext<TConfig>;
abstract initialize(context: ModuleContext<TConfig>): Promise<void>;
abstract destroy(): Promise<void>;
setContext(context: ModuleContext<TConfig>): void {
this.context = context;
}
protected getDependency<T>(name: string): T {
const dependency = this.context.dependencies[name];
if (!dependency) {
throw new Error(`Dependency '${name}' not found`);
}
return dependency;
}
protected emit<K extends string>(event: K, payload: any): void {
this.context.emit(event, payload);
}
protected subscribe<K extends string>(
event: K,
handler: (payload: any) => void
): () => void {
return this.context.subscribe(event, handler);
}
}
class ModuleRegistry {
private modules = new Map<string, BaseModule>();
private dependencies = new Map<string, Set<string>>();
private eventBus = new EventTarget();
async registerModule<T extends BaseModule>(
ModuleClass: new () => T,
config?: any
): Promise<void> {
const module = new ModuleClass();
const metadata = module.metadata;
// Validate dependencies
await this.validateDependencies(metadata.dependencies);
// Prepare context
const context: ModuleContext = {
config: config || {},
dependencies: this.resolveDependencies(metadata.dependencies),
emit: (event, payload) => this.emit(event, payload),
subscribe: (event, handler) => this.subscribe(event, handler)
};
module.setContext(context);
await module.initialize(context);
this.modules.set(metadata.name, module);
this.dependencies.set(metadata.name, new Set(metadata.dependencies));
}
async unregisterModule(name: string): Promise<void> {
const module = this.modules.get(name);
if (module) {
await module.destroy();
this.modules.delete(name);
this.dependencies.delete(name);
}
}
getModule<T extends BaseModule>(name: string): T | undefined {
return this.modules.get(name) as T;
}
private async validateDependencies(dependencies: string[]): Promise<void> {
for (const dep of dependencies) {
if (!this.modules.has(dep)) {
throw new Error(`Dependency '${dep}' not found`);
}
}
}
private resolveDependencies(dependencies: string[]): Record<string, any> {
const resolved: Record<string, any> = {};
for (const dep of dependencies) {
const module = this.modules.get(dep);
if (module) {
resolved[dep] = module.metadata.exports;
}
}
return resolved;
}
private emit(event: string, payload: any): void {
this.eventBus.dispatchEvent(
new CustomEvent(event, { detail: payload })
);
}
private subscribe(event: string, handler: (payload: any) => void): () => void {
const listener = (e: CustomEvent) => handler(e.detail);
this.eventBus.addEventListener(event, listener as EventListener);
return () => {
this.eventBus.removeEventListener(event, listener as EventListener);
};
}
}
// Example modules
interface AuthConfig {
providers: Array<'google' | 'facebook' | 'github'>;
sessionTimeout: number;
}
class AuthModule extends BaseModule<AuthConfig> {
readonly metadata: ModuleMetadata = {
name: 'auth',
version: '1.0.0',
dependencies: [],
exports: {
login: this.login.bind(this),
logout: this.logout.bind(this),
getCurrentUser: this.getCurrentUser.bind(this)
}
};
private currentUser: User | null = null;
async initialize(context: ModuleContext<AuthConfig>): Promise<void> {
console.log('Auth module initialized with config:', context.config);
// Setup session timeout
setInterval(() => {
this.checkSessionTimeout();
}, 60000); // Check every minute
}
async destroy(): Promise<void> {
this.currentUser = null;
}
async login(credentials: { email: string; password: string }): Promise<User> {
// Authentication logic
const user = await this.authenticateUser(credentials);
this.currentUser = user;
this.emit('user:login', { user });
return user;
}
async logout(): Promise<void> {
const user = this.currentUser;
this.currentUser = null;
this.emit('user:logout', { user });
}
getCurrentUser(): User | null {
return this.currentUser;
}
private async authenticateUser(credentials: any): Promise<User> {
// Mock authentication
return {
id: '1',
email: credentials.email,
firstName: 'John',
lastName: 'Doe',
role: 'user',
createdAt: new Date()
};
}
private checkSessionTimeout(): void {
if (this.currentUser) {
// Check if session expired based on config
const timeout = this.context.config.sessionTimeout;
// Implementation details...
}
}
}
interface NotificationConfig {
providers: Array<'email' | 'push' | 'sms'>;
templates: Record<string, string>;
}
class NotificationModule extends BaseModule<NotificationConfig> {
readonly metadata: ModuleMetadata = {
name: 'notification',
version: '1.0.0',
dependencies: ['auth'],
exports: {
send: this.send.bind(this),
subscribe: this.subscribeToNotifications.bind(this)
}
};
private authModule!: any;
async initialize(context: ModuleContext<NotificationConfig>): Promise<void> {
this.authModule = this.getDependency('auth');
// Listen to auth events
this.subscribe('user:login', (payload) => {
this.send({
type: 'welcome',
recipient: payload.user.email,
data: { name: payload.user.firstName }
});
});
}
async destroy(): Promise<void> {
// Cleanup
}
async send(notification: {
type: string;
recipient: string;
data: any;
}): Promise<void> {
const template = this.context.config.templates[notification.type];
if (!template) {
throw new Error(`Template '${notification.type}' not found`);
}
// Send notification logic
console.log('Sending notification:', notification);
}
subscribeToNotifications(userId: string): void {
// Subscription logic
}
}
// Angular integration
@Injectable({ providedIn: 'root' })
export class ModuleSystemService {
private registry = new ModuleRegistry();
async initializeApplication(): Promise<void> {
try {
// Register modules in dependency order
await this.registry.registerModule(AuthModule, {
providers: ['google', 'github'],
sessionTimeout: 3600000 // 1 hour
});
await this.registry.registerModule(NotificationModule, {
providers: ['email', 'push'],
templates: {
welcome: 'Welcome {{name}}!',
passwordReset: 'Reset your password: {{link}}'
}
});
console.log('All modules initialized successfully');
} catch (error) {
console.error('Failed to initialize modules:', error);
throw error;
}
}
getAuthModule(): AuthModule | undefined {
return this.registry.getModule<AuthModule>('auth');
}
getNotificationModule(): NotificationModule | undefined {
return this.registry.getModule<NotificationModule>('notification');
}
async shutdownApplication(): Promise<void> {
await this.registry.unregisterModule('notification');
await this.registry.unregisterModule('auth');
}
}
// Usage in Angular app
@Component({
selector: 'app-root',
template: '...'
})
export class AppComponent implements OnInit, OnDestroy {
constructor(private moduleSystem: ModuleSystemService) {}
async ngOnInit(): Promise<void> {
await this.moduleSystem.initializeApplication();
}
async ngOnDestroy(): Promise<void> {
await this.moduleSystem.shutdownApplication();
}
async login(credentials: { email: string; password: string }): Promise<void> {
const authModule = this.moduleSystem.getAuthModule();
if (authModule) {
await authModule.metadata.exports.login(credentials);
}
}
}
⚡ Mid-Level Challenges
Challenge 6: Foundational Pattern Mastery
Companies: Spotify, Slack, GitHub
/**
* CHALLENGE: Implement essential TypeScript patterns:
* 1. Builder pattern with type safety
* 2. Observer pattern with generic events
* 3. Strategy pattern with compile-time validation
* 4. Factory pattern with type inference
* 5. Decorator pattern with method enhancement
*/
// SOLUTION: Builder Pattern
interface QueryBuilder<T> {
select<K extends keyof T>(...fields: K[]): QueryBuilder<Pick<T, K>>;
where<K extends keyof T>(field: K, operator: '=' | '!=' | '>' | '<', value: T[K]): this;
orderBy<K extends keyof T>(field: K, direction: 'asc' | 'desc'): this;
limit(count: number): this;
build(): Query<T>;
}
interface Query<T> {
selection: (keyof T)[];
conditions: WhereCondition<T>[];
ordering: OrderBy<T>[];
limitCount?: number;
}
interface WhereCondition<T> {
field: keyof T;
operator: '=' | '!=' | '>' | '<';
value: T[keyof T];
}
interface OrderBy<T> {
field: keyof T;
direction: 'asc' | 'desc';
}
class TypeSafeQueryBuilder<T> implements QueryBuilder<T> {
private selection: (keyof T)[] = [];
private conditions: WhereCondition<T>[] = [];
private ordering: OrderBy<T>[] = [];
private limitCount?: number;
select<K extends keyof T>(...fields: K[]): QueryBuilder<Pick<T, K>> {
this.selection = fields;
return this as any;
}
where<K extends keyof T>(field: K, operator: '=' | '!=' | '>' | '<', value: T[K]): this {
this.conditions.push({ field, operator, value });
return this;
}
orderBy<K extends keyof T>(field: K, direction: 'asc' | 'desc'): this {
this.ordering.push({ field, direction });
return this;
}
limit(count: number): this {
this.limitCount = count;
return this;
}
build(): Query<T> {
return {
selection: this.selection,
conditions: this.conditions,
ordering: this.ordering,
limitCount: this.limitCount
};
}
}
// Observer Pattern
type EventListener<T> = (data: T) => void;
interface Observable<TEvents extends Record<string, any>> {
on<K extends keyof TEvents>(event: K, listener: EventListener<TEvents[K]>): () => void;
emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void;
off<K extends keyof TEvents>(event: K, listener: EventListener<TEvents[K]>): void;
}
class EventEmitter<TEvents extends Record<string, any>> implements Observable<TEvents> {
private listeners = new Map<keyof TEvents, Set<EventListener<any>>>();
on<K extends keyof TEvents>(event: K, listener: EventListener<TEvents[K]>): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(listener);
return () => this.off(event, listener);
}
emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
eventListeners.forEach(listener => listener(data));
}
}
off<K extends keyof TEvents>(event: K, listener: EventListener<TEvents[K]>): void {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
eventListeners.delete(listener);
}
}
}
// Strategy Pattern
interface ValidationStrategy<T> {
validate(value: T): { valid: boolean; errors: string[] };
}
class EmailValidationStrategy implements ValidationStrategy<string> {
validate(value: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (!value) {
errors.push('Email is required');
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
errors.push('Invalid email format');
}
return { valid: errors.length === 0, errors };
}
}
class PasswordValidationStrategy implements ValidationStrategy<string> {
validate(value: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (!value) {
errors.push('Password is required');
} else {
if (value.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(value)) {
errors.push('Password must contain uppercase letter');
}
if (!/[a-z]/.test(value)) {
errors.push('Password must contain lowercase letter');
}
if (!/\d/.test(value)) {
errors.push('Password must contain number');
}
}
return { valid: errors.length === 0, errors };
}
}
class ValidationContext<T> {
constructor(private strategy: ValidationStrategy<T>) {}
setStrategy(strategy: ValidationStrategy<T>): void {
this.strategy = strategy;
}
validate(value: T): { valid: boolean; errors: string[] } {
return this.strategy.validate(value);
}
}
// Factory Pattern
type UserRole = 'admin' | 'user' | 'moderator';
interface UserFactory<T extends UserRole> {
createUser(data: UserData): UserByRole<T>;
}
type UserByRole<T extends UserRole> =
T extends 'admin' ? AdminUser :
T extends 'moderator' ? ModeratorUser :
T extends 'user' ? RegularUser :
never;
interface UserData {
email: string;
firstName: string;
lastName: string;
}
interface BaseUser extends UserData {
id: string;
role: UserRole;
createdAt: Date;
}
interface RegularUser extends BaseUser {
role: 'user';
}
interface ModeratorUser extends BaseUser {
role: 'moderator';
permissions: string[];
}
interface AdminUser extends BaseUser {
role: 'admin';
permissions: string[];
systemAccess: boolean;
}
class UserFactoryImpl<T extends UserRole> implements UserFactory<T> {
constructor(private role: T) {}
createUser(data: UserData): UserByRole<T> {
const baseUser = {
id: crypto.randomUUID(),
...data,
role: this.role,
createdAt: new Date()
};
switch (this.role) {
case 'admin':
return {
...baseUser,
role: 'admin',
permissions: ['read', 'write', 'delete', 'admin'],
systemAccess: true
} as UserByRole<T>;
case 'moderator':
return {
...baseUser,
role: 'moderator',
permissions: ['read', 'write', 'moderate']
} as UserByRole<T>;
case 'user':
default:
return {
...baseUser,
role: 'user'
} as UserByRole<T>;
}
}
}
// Usage in Angular service
@Injectable({ providedIn: 'root' })
export class UserManagementService {
private userEvents = new EventEmitter<{
userCreated: { user: BaseUser };
userUpdated: { user: BaseUser };
userDeleted: { userId: string };
}>();
// Factory instances
private adminFactory = new UserFactoryImpl('admin');
private moderatorFactory = new UserFactoryImpl('moderator');
private userFactory = new UserFactoryImpl('user');
// Validation contexts
private emailValidator = new ValidationContext(new EmailValidationStrategy());
private passwordValidator = new ValidationContext(new PasswordValidationStrategy());
constructor() {
// Subscribe to user events
this.userEvents.on('userCreated', ({ user }) => {
console.log('User created:', user);
});
}
createUser<T extends UserRole>(
role: T,
userData: UserData
): UserByRole<T> {
// Validate email
const emailValidation = this.emailValidator.validate(userData.email);
if (!emailValidation.valid) {
throw new Error(`Invalid email: ${emailValidation.errors.join(', ')}`);
}
// Create user with appropriate factory
let user: UserByRole<T>;
switch (role) {
case 'admin':
user = this.adminFactory.createUser(userData) as UserByRole<T>;
break;
case 'moderator':
user = this.moderatorFactory.createUser(userData) as UserByRole<T>;
break;
case 'user':
default:
user = this.userFactory.createUser(userData) as UserByRole<T>;
break;
}
// Emit event
this.userEvents.emit('userCreated', { user });
return user;
}
buildUserQuery(): QueryBuilder<BaseUser> {
return new TypeSafeQueryBuilder<BaseUser>();
}
subscribeToUserEvents(): () => void {
return this.userEvents.on('userCreated', ({ user }) => {
// Handle user creation
console.log('New user created:', user);
});
}
}
// Component usage
@Component({
selector: 'app-user-form',
template: `
<form (ngSubmit)="onSubmit()">
<input [(ngModel)]="formData.email" placeholder="Email">
<input [(ngModel)]="formData.firstName" placeholder="First Name">
<input [(ngModel)]="formData.lastName" placeholder="Last Name">
<select [(ngModel)]="selectedRole">
<option value="user">User</option>
<option value="moderator">Moderator</option>
<option value="admin">Admin</option>
</select>
<button type="submit">Create User</button>
</form>
`
})
export class UserFormComponent implements OnInit, OnDestroy {
formData: UserData = {
email: '',
firstName: '',
lastName: ''
};
selectedRole: UserRole = 'user';
private unsubscribe?: () => void;
constructor(private userService: UserManagementService) {}
ngOnInit(): void {
this.unsubscribe = this.userService.subscribeToUserEvents();
}
ngOnDestroy(): void {
this.unsubscribe?.();
}
onSubmit(): void {
try {
const user = this.userService.createUser(this.selectedRole, this.formData);
console.log('User created successfully:', user);
// Build and execute query
const query = this.userService.buildUserQuery()
.select('id', 'email', 'role')
.where('role', '=', this.selectedRole)
.orderBy('createdAt', 'desc')
.limit(10)
.build();
console.log('Query built:', query);
} catch (error) {
console.error('Failed to create user:', error);
}
}
}
🛠️ PRACTICAL EXERCISES
💻 Exercise 1: Type-Safe Configuration System
Objective: Build a type-safe configuration management system for Angular applications.
Step 1: Define Configuration Schema
// Create interfaces for different environments
interface DatabaseConfig {
host: string;
port: number;
database: string;
ssl: boolean;
poolSize: number;
}
interface APIConfig {
baseUrl: string;
timeout: number;
retries: number;
apiKey?: string;
}
interface FeatureFlags {
enableNewUI: boolean;
enableAnalytics: boolean;
enableBetaFeatures: boolean;
}
interface AppConfig {
environment: 'development' | 'staging' | 'production';
database: DatabaseConfig;
api: APIConfig;
features: FeatureFlags;
logging: {
level: 'debug' | 'info' | 'warn' | 'error';
enableConsole: boolean;
enableRemote: boolean;
};
}
// YOUR TASK: Complete the implementation
type ConfigValidator<T> = {
[K in keyof T]: (value: T[K]) => boolean;
};
type PartialConfig<T> = {
[K in keyof T]?: T[K] extends object ? PartialConfig<T[K]> : T[K];
};
type ConfigPaths<T> = /* TODO: Implement path extraction type */;
Step 2: Implementation (Solution)
// Configuration validator
type ConfigValidator<T> = {
[K in keyof T]: (value: T[K]) => boolean;
};
// Deep partial for configuration overrides
type PartialConfig<T> = {
[K in keyof T]?: T[K] extends object ? PartialConfig<T[K]> : T[K];
};
// Path extraction for type-safe access
type ConfigPaths<T, D extends number = 5> = [D] extends [never]
? never
: T extends object
? {
[K in keyof T]-?: K extends string | number
? `${K}` | `${K}.${ConfigPaths<T[K], Prev[D]>}`
: never;
}[keyof T]
: '';
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
type GetConfigValue<T, P extends string> = P extends `${infer K}.${infer Rest}`
? K extends keyof T
? GetConfigValue<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
class TypeSafeConfigManager<T extends Record<string, any>> {
private config: T;
private validators: Partial<ConfigValidator<T>> = {};
constructor(defaultConfig: T) {
this.config = { ...defaultConfig };
}
// Set validators for configuration validation
setValidator<K extends keyof T>(key: K, validator: (value: T[K]) => boolean): this {
this.validators[key] = validator;
return this;
}
// Update configuration with validation
updateConfig(updates: PartialConfig<T>): void {
const newConfig = this.deepMerge(this.config, updates);
// Validate updated config
for (const [key, validator] of Object.entries(this.validators)) {
if (validator && newConfig[key] !== undefined) {
if (!validator(newConfig[key])) {
throw new Error(`Configuration validation failed for key: ${key}`);
}
}
}
this.config = newConfig;
}
// Type-safe configuration access
get<P extends ConfigPaths<T>>(path: P): GetConfigValue<T, P> {
return this.getValueByPath(this.config, path);
}
// Get full configuration
getConfig(): T {
return { ...this.config };
}
private deepMerge<A, B>(target: A, source: B): A & B {
const result = { ...target } as A & B;
for (const key in source) {
if (source.hasOwnProperty(key)) {
const sourceValue = source[key];
const targetValue = (target as any)[key];
if (this.isObject(sourceValue) && this.isObject(targetValue)) {
(result as any)[key] = this.deepMerge(targetValue, sourceValue);
} else {
(result as any)[key] = sourceValue;
}
}
}
return result;
}
private isObject(item: any): boolean {
return item && typeof item === 'object' && !Array.isArray(item);
}
private getValueByPath(obj: any, path: string): any {
return path.split('.').reduce((current, key) => current?.[key], obj);
}
}
// Angular service implementation
@Injectable({ providedIn: 'root' })
export class ConfigService {
private configManager: TypeSafeConfigManager<AppConfig>;
constructor() {
const defaultConfig: AppConfig = {
environment: 'development',
database: {
host: 'localhost',
port: 5432,
database: 'myapp',
ssl: false,
poolSize: 10
},
api: {
baseUrl: 'http://localhost:3000',
timeout: 5000,
retries: 3
},
features: {
enableNewUI: false,
enableAnalytics: true,
enableBetaFeatures: false
},
logging: {
level: 'debug',
enableConsole: true,
enableRemote: false
}
};
this.configManager = new TypeSafeConfigManager(defaultConfig);
this.setupValidators();
}
private setupValidators(): void {
this.configManager
.setValidator('database', (db) => db.port > 0 && db.port < 65536)
.setValidator('api', (api) => api.timeout > 0 && api.retries >= 0);
}
// Type-safe configuration access
getDatabaseHost(): string {
return this.configManager.get('database.host');
}
getAPIBaseUrl(): string {
return this.configManager.get('api.baseUrl');
}
isFeatureEnabled(feature: keyof AppConfig['features']): boolean {
return this.configManager.get(`features.${feature}` as any);
}
updateEnvironment(env: AppConfig['environment']): void {
const envConfigs = {
development: {
api: { baseUrl: 'http://localhost:3000' },
logging: { level: 'debug' as const, enableConsole: true }
},
staging: {
api: { baseUrl: 'https://staging-api.example.com' },
logging: { level: 'info' as const, enableRemote: true }
},
production: {
api: { baseUrl: 'https://api.example.com' },
logging: { level: 'error' as const, enableRemote: true, enableConsole: false }
}
};
this.configManager.updateConfig({
environment: env,
...envConfigs[env]
});
}
}
💻 Exercise 2: Advanced Form Validation System
Objective: Create a reusable, type-safe form validation system with cross-field validation.
Step 1: Define Validation Framework
// YOUR TASK: Complete these type definitions
interface ValidationRule<T> {
validate: (value: T) => ValidationResult;
message: string;
}
interface CrossFieldValidation<T> {
fields: Array<keyof T>;
validate: (formValue: T) => ValidationResult;
message: string;
}
interface ValidationResult {
isValid: boolean;
errors: string[];
}
// TODO: Implement ValidatedForm class
class ValidatedForm<T extends Record<string, any>> {
// Implementation needed
}
Step 2: Implementation (Solution)
interface ValidationRule<T> {
validate: (value: T) => ValidationResult;
message: string;
}
interface CrossFieldValidation<T> {
fields: Array<keyof T>;
validate: (formValue: T) => ValidationResult;
message: string;
}
interface ValidationResult {
isValid: boolean;
errors: string[];
}
interface FieldConfig<T, K extends keyof T> {
rules: ValidationRule<T[K]>[];
crossFieldRules?: CrossFieldValidation<T>[];
}
class ValidationRuleBuilder<T> {
static required<T>(message = 'This field is required'): ValidationRule<T> {
return {
validate: (value: T) => ({
isValid: value !== null && value !== undefined && value !== '',
errors: value !== null && value !== undefined && value !== '' ? [] : [message]
}),
message
};
}
static minLength(min: number, message?: string): ValidationRule<string> {
return {
validate: (value: string) => ({
isValid: !value || value.length >= min,
errors: !value || value.length >= min ? [] : [message || `Minimum length is ${min}`]
}),
message: message || `Minimum length is ${min}`
};
}
static email(message = 'Please enter a valid email'): ValidationRule<string> {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return {
validate: (value: string) => ({
isValid: !value || emailRegex.test(value),
errors: !value || emailRegex.test(value) ? [] : [message]
}),
message
};
}
static pattern(regex: RegExp, message: string): ValidationRule<string> {
return {
validate: (value: string) => ({
isValid: !value || regex.test(value),
errors: !value || regex.test(value) ? [] : [message]
}),
message
};
}
static custom<T>(
validator: (value: T) => boolean,
message: string
): ValidationRule<T> {
return {
validate: (value: T) => ({
isValid: validator(value),
errors: validator(value) ? [] : [message]
}),
message
};
}
}
class CrossFieldRuleBuilder<T> {
static confirmPassword<T extends { password: string; confirmPassword: string }>(): CrossFieldValidation<T> {
return {
fields: ['password', 'confirmPassword'],
validate: (formValue: T) => ({
isValid: formValue.password === formValue.confirmPassword,
errors: formValue.password === formValue.confirmPassword ? [] : ['Passwords do not match']
}),
message: 'Passwords do not match'
};
}
static dateRange<T extends Record<string, any>>(
startField: keyof T,
endField: keyof T,
message = 'End date must be after start date'
): CrossFieldValidation<T> {
return {
fields: [startField, endField],
validate: (formValue: T) => {
const startDate = new Date(formValue[startField]);
const endDate = new Date(formValue[endField]);
return {
isValid: startDate <= endDate,
errors: startDate <= endDate ? [] : [message]
};
},
message
};
}
static conditional<T extends Record<string, any>>(
dependentField: keyof T,
requiredField: keyof T,
condition: (value: T[keyof T]) => boolean,
message: string
): CrossFieldValidation<T> {
return {
fields: [dependentField, requiredField],
validate: (formValue: T) => {
const shouldRequire = condition(formValue[dependentField]);
const hasValue = formValue[requiredField] !== null &&
formValue[requiredField] !== undefined &&
formValue[requiredField] !== '';
return {
isValid: !shouldRequire || hasValue,
errors: !shouldRequire || hasValue ? [] : [message]
};
},
message
};
}
}
class ValidatedForm<T extends Record<string, any>> {
private fieldConfigs: Partial<Record<keyof T, FieldConfig<T, keyof T>>> = {};
private crossFieldRules: CrossFieldValidation<T>[] = [];
private formValue: Partial<T> = {};
private fieldErrors: Record<keyof T, string[]> = {} as Record<keyof T, string[]>;
private touched: Record<keyof T, boolean> = {} as Record<keyof T, boolean>;
// Configure field validation
field<K extends keyof T>(
fieldName: K,
config: FieldConfig<T, K>
): this {
this.fieldConfigs[fieldName] = config;
return this;
}
// Add cross-field validation
addCrossFieldRule(rule: CrossFieldValidation<T>): this {
this.crossFieldRules.push(rule);
return this;
}
// Set field value and validate
setValue<K extends keyof T>(fieldName: K, value: T[K]): void {
this.formValue[fieldName] = value;
this.validateField(fieldName);
this.validateCrossFields();
}
// Mark field as touched
touch<K extends keyof T>(fieldName: K): void {
this.touched[fieldName] = true;
}
// Get field value
getValue<K extends keyof T>(fieldName: K): T[K] | undefined {
return this.formValue[fieldName];
}
// Get field errors
getFieldErrors<K extends keyof T>(fieldName: K): string[] {
return this.fieldErrors[fieldName] || [];
}
// Check if field has errors
hasFieldErrors<K extends keyof T>(fieldName: K): boolean {
return this.getFieldErrors(fieldName).length > 0;
}
// Check if field should show errors (touched and has errors)
shouldShowErrors<K extends keyof T>(fieldName: K): boolean {
return this.touched[fieldName] && this.hasFieldErrors(fieldName);
}
// Check if entire form is valid
isValid(): boolean {
return Object.values(this.fieldErrors).every(errors => errors.length === 0);
}
// Get form value
getFormValue(): Partial<T> {
return { ...this.formValue };
}
// Validate specific field
private validateField<K extends keyof T>(fieldName: K): void {
const config = this.fieldConfigs[fieldName];
if (!config) return;
const fieldValue = this.formValue[fieldName];
const errors: string[] = [];
for (const rule of config.rules) {
const result = rule.validate(fieldValue as any);
if (!result.isValid) {
errors.push(...result.errors);
}
}
this.fieldErrors[fieldName] = errors;
}
// Validate cross-field rules
private validateCrossFields(): void {
for (const rule of this.crossFieldRules) {
const result = rule.validate(this.formValue as T);
if (!result.isValid) {
// Add errors to all affected fields
for (const field of rule.fields) {
if (!this.fieldErrors[field]) {
this.fieldErrors[field] = [];
}
// Remove existing cross-field errors for this rule
this.fieldErrors[field] = this.fieldErrors[field].filter(
error => error !== rule.message
);
// Add new cross-field error
this.fieldErrors[field].push(...result.errors);
}
} else {
// Remove cross-field errors when validation passes
for (const field of rule.fields) {
if (this.fieldErrors[field]) {
this.fieldErrors[field] = this.fieldErrors[field].filter(
error => error !== rule.message
);
}
}
}
}
}
// Validate all fields
validateAll(): boolean {
for (const fieldName of Object.keys(this.fieldConfigs) as Array<keyof T>) {
this.validateField(fieldName);
this.touch(fieldName);
}
this.validateCrossFields();
return this.isValid();
}
}
// Usage in Angular component
interface RegistrationFormData {
email: string;
password: string;
confirmPassword: string;
firstName: string;
lastName: string;
birthDate: string;
agreeToTerms: boolean;
}
@Component({
selector: 'app-registration',
template: `
<form (ngSubmit)="onSubmit()">
<div class="field">
<label>Email</label>
<input
type="email"
[value]="form.getValue('email') || ''"
(input)="onFieldChange('email', $event)"
(blur)="form.touch('email')"
[class.error]="form.shouldShowErrors('email')">
<div class="errors" *ngIf="form.shouldShowErrors('email')">
<span *ngFor="let error of form.getFieldErrors('email')">{{ error }}</span>
</div>
</div>
<div class="field">
<label>Password</label>
<input
type="password"
[value]="form.getValue('password') || ''"
(input)="onFieldChange('password', $event)"
(blur)="form.touch('password')"
[class.error]="form.shouldShowErrors('password')">
<div class="errors" *ngIf="form.shouldShowErrors('password')">
<span *ngFor="let error of form.getFieldErrors('password')">{{ error }}</span>
</div>
</div>
<div class="field">
<label>Confirm Password</label>
<input
type="password"
[value]="form.getValue('confirmPassword') || ''"
(input)="onFieldChange('confirmPassword', $event)"
(blur)="form.touch('confirmPassword')"
[class.error]="form.shouldShowErrors('confirmPassword')">
<div class="errors" *ngIf="form.shouldShowErrors('confirmPassword')">
<span *ngFor="let error of form.getFieldErrors('confirmPassword')">{{ error }}</span>
</div>
</div>
<button type="submit" [disabled]="!form.isValid()">
Register
</button>
</form>
`,
styleUrls: ['./registration.component.scss']
})
export class RegistrationComponent implements OnInit {
form = new ValidatedForm<RegistrationFormData>();
ngOnInit(): void {
this.setupValidation();
}
private setupValidation(): void {
this.form
.field('email', {
rules: [
ValidationRuleBuilder.required(),
ValidationRuleBuilder.email()
]
})
.field('password', {
rules: [
ValidationRuleBuilder.required(),
ValidationRuleBuilder.minLength(8),
ValidationRuleBuilder.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
'Password must contain uppercase, lowercase, and number'
)
]
})
.field('confirmPassword', {
rules: [ValidationRuleBuilder.required()]
})
.field('firstName', {
rules: [
ValidationRuleBuilder.required(),
ValidationRuleBuilder.minLength(2)
]
})
.field('lastName', {
rules: [
ValidationRuleBuilder.required(),
ValidationRuleBuilder.minLength(2)
]
})
.field('agreeToTerms', {
rules: [
ValidationRuleBuilder.custom(
(value: boolean) => value === true,
'You must agree to the terms'
)
]
})
.addCrossFieldRule(CrossFieldRuleBuilder.confirmPassword());
}
onFieldChange(fieldName: keyof RegistrationFormData, event: Event): void {
const target = event.target as HTMLInputElement;
let value: any = target.value;
// Handle boolean fields
if (fieldName === 'agreeToTerms') {
value = target.checked;
}
this.form.setValue(fieldName, value);
}
onSubmit(): void {
if (this.form.validateAll()) {
const formData = this.form.getFormValue();
console.log('Form submitted:', formData);
// Handle form submission
} else {
console.log('Form has errors');
}
}
}
💻 Exercise 3: Type-Safe State Management
Objective: Build a Redux-like state management system with full TypeScript support.
Step 1: Define State and Actions
// YOUR TASK: Complete the type definitions
interface AppState {
user: {
profile: User | null;
preferences: UserPreferences;
};
todos: {
items: Todo[];
filter: 'all' | 'active' | 'completed';
};
ui: {
loading: Record<string, boolean>;
errors: Record<string, string | null>;
};
}
// TODO: Define action types with payloads
type Actions =
| { type: 'SET_USER'; payload: User }
| { type: 'CLEAR_USER' }
// Add more action types...
// TODO: Implement Store class with type safety
class Store<S, A> {
// Implementation needed
}
Step 2: Complete Implementation (Solution)
interface AppState {
user: {
profile: User | null;
preferences: UserPreferences;
};
todos: {
items: Todo[];
filter: 'all' | 'active' | 'completed';
};
ui: {
loading: Record<string, boolean>;
errors: Record<string, string | null>;
};
}
interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: Date;
}
interface UserPreferences {
theme: 'light' | 'dark';
notifications: boolean;
language: 'en' | 'es' | 'fr';
}
// Complete action definitions
type Actions =
| { type: 'SET_USER'; payload: User }
| { type: 'CLEAR_USER' }
| { type: 'UPDATE_PREFERENCES'; payload: Partial<UserPreferences> }
| { type: 'ADD_TODO'; payload: Omit<Todo, 'id' | 'createdAt'> }
| { type: 'TOGGLE_TODO'; payload: { id: string } }
| { type: 'DELETE_TODO'; payload: { id: string } }
| { type: 'SET_FILTER'; payload: AppState['todos']['filter'] }
| { type: 'SET_LOADING'; payload: { key: string; loading: boolean } }
| { type: 'SET_ERROR'; payload: { key: string; error: string | null } };
// Type-safe reducer
type Reducer<S, A> = (state: S, action: A) => S;
// Selector type
type Selector<S, R> = (state: S) => R;
// Store implementation
class Store<S, A> {
private state: S;
private reducer: Reducer<S, A>;
private listeners: Set<() => void> = new Set();
constructor(initialState: S, reducer: Reducer<S, A>) {
this.state = initialState;
this.reducer = reducer;
}
getState(): S {
return this.state;
}
dispatch(action: A): void {
this.state = this.reducer(this.state, action);
this.notifyListeners();
}
subscribe(listener: () => void): () => void {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
select<R>(selector: Selector<S, R>): R {
return selector(this.state);
}
private notifyListeners(): void {
this.listeners.forEach(listener => listener());
}
}
// Action creators with type safety
const actionCreators = {
setUser: (user: User): Actions => ({
type: 'SET_USER',
payload: user
}),
clearUser: (): Actions => ({
type: 'CLEAR_USER'
}),
updatePreferences: (preferences: Partial<UserPreferences>): Actions => ({
type: 'UPDATE_PREFERENCES',
payload: preferences
}),
addTodo: (todo: Omit<Todo, 'id' | 'createdAt'>): Actions => ({
type: 'ADD_TODO',
payload: todo
}),
toggleTodo: (id: string): Actions => ({
type: 'TOGGLE_TODO',
payload: { id }
}),
deleteTodo: (id: string): Actions => ({
type: 'DELETE_TODO',
payload: { id }
}),
setFilter: (filter: AppState['todos']['filter']): Actions => ({
type: 'SET_FILTER',
payload: filter
}),
setLoading: (key: string, loading: boolean): Actions => ({
type: 'SET_LOADING',
payload: { key, loading }
}),
setError: (key: string, error: string | null): Actions => ({
type: 'SET_ERROR',
payload: { key, error }
})
};
// Main reducer
const appReducer: Reducer<AppState, Actions> = (state, action) => {
switch (action.type) {
case 'SET_USER':
return {
...state,
user: {
...state.user,
profile: action.payload
}
};
case 'CLEAR_USER':
return {
...state,
user: {
...state.user,
profile: null
}
};
case 'UPDATE_PREFERENCES':
return {
...state,
user: {
...state.user,
preferences: {
...state.user.preferences,
...action.payload
}
}
};
case 'ADD_TODO':
const newTodo: Todo = {
id: crypto.randomUUID(),
createdAt: new Date(),
completed: false,
...action.payload
};
return {
...state,
todos: {
...state.todos,
items: [...state.todos.items, newTodo]
}
};
case 'TOGGLE_TODO':
return {
...state,
todos: {
...state.todos,
items: state.todos.items.map(todo =>
todo.id === action.payload.id
? { ...todo, completed: !todo.completed }
: todo
)
}
};
case 'DELETE_TODO':
return {
...state,
todos: {
...state.todos,
items: state.todos.items.filter(todo => todo.id !== action.payload.id)
}
};
case 'SET_FILTER':
return {
...state,
todos: {
...state.todos,
filter: action.payload
}
};
case 'SET_LOADING':
return {
...state,
ui: {
...state.ui,
loading: {
...state.ui.loading,
[action.payload.key]: action.payload.loading
}
}
};
case 'SET_ERROR':
return {
...state,
ui: {
...state.ui,
errors: {
...state.ui.errors,
[action.payload.key]: action.payload.error
}
}
};
default:
return state;
}
};
// Selectors
const selectors = {
getCurrentUser: (state: AppState) => state.user.profile,
getUserPreferences: (state: AppState) => state.user.preferences,
getAllTodos: (state: AppState) => state.todos.items,
getFilteredTodos: (state: AppState) => {
const { items, filter } = state.todos;
switch (filter) {
case 'active':
return items.filter(todo => !todo.completed);
case 'completed':
return items.filter(todo => todo.completed);
default:
return items;
}
},
getTodoFilter: (state: AppState) => state.todos.filter,
isLoading: (key: string) => (state: AppState) => state.ui.loading[key] || false,
getError: (key: string) => (state: AppState) => state.ui.errors[key] || null
};
// Angular service integration
@Injectable({ providedIn: 'root' })
export class StateService {
private store: Store<AppState, Actions>;
constructor() {
const initialState: AppState = {
user: {
profile: null,
preferences: {
theme: 'light',
notifications: true,
language: 'en'
}
},
todos: {
items: [],
filter: 'all'
},
ui: {
loading: {},
errors: {}
}
};
this.store = new Store(initialState, appReducer);
}
// Action dispatchers
setUser(user: User): void {
this.store.dispatch(actionCreators.setUser(user));
}
clearUser(): void {
this.store.dispatch(actionCreators.clearUser());
}
updatePreferences(preferences: Partial<UserPreferences>): void {
this.store.dispatch(actionCreators.updatePreferences(preferences));
}
addTodo(text: string): void {
this.store.dispatch(actionCreators.addTodo({ text, completed: false }));
}
toggleTodo(id: string): void {
this.store.dispatch(actionCreators.toggleTodo(id));
}
deleteTodo(id: string): void {
this.store.dispatch(actionCreators.deleteTodo(id));
}
setTodoFilter(filter: AppState['todos']['filter']): void {
this.store.dispatch(actionCreators.setFilter(filter));
}
setLoading(key: string, loading: boolean): void {
this.store.dispatch(actionCreators.setLoading(key, loading));
}
setError(key: string, error: string | null): void {
this.store.dispatch(actionCreators.setError(key, error));
}
// Selectors
getCurrentUser(): User | null {
return this.store.select(selectors.getCurrentUser);
}
getUserPreferences(): UserPreferences {
return this.store.select(selectors.getUserPreferences);
}
getAllTodos(): Todo[] {
return this.store.select(selectors.getAllTodos);
}
getFilteredTodos(): Todo[] {
return this.store.select(selectors.getFilteredTodos);
}
getTodoFilter(): AppState['todos']['filter'] {
return this.store.select(selectors.getTodoFilter);
}
isLoading(key: string): boolean {
return this.store.select(selectors.isLoading(key));
}
getError(key: string): string | null {
return this.store.select(selectors.getError(key));
}
// Subscribe to state changes
subscribe(listener: () => void): () => void {
return this.store.subscribe(listener);
}
// Get current state (for debugging)
getState(): AppState {
return this.store.getState();
}
}
// Component usage
@Component({
selector: 'app-todo-list',
template: `
<div class="todo-app">
<input
[(ngModel)]="newTodoText"
(keyup.enter)="addTodo()"
placeholder="Add a todo...">
<div class="filters">
<button
*ngFor="let filter of filters"
[class.active]="currentFilter === filter"
(click)="setFilter(filter)">
{{ filter | titlecase }}
</button>
</div>
<ul class="todo-list">
<li
*ngFor="let todo of filteredTodos; trackBy: trackByTodoId"
[class.completed]="todo.completed">
<input
type="checkbox"
[checked]="todo.completed"
(change)="toggleTodo(todo.id)">
<span>{{ todo.text }}</span>
<button (click)="deleteTodo(todo.id)">Delete</button>
</li>
</ul>
</div>
`
})
export class TodoListComponent implements OnInit, OnDestroy {
newTodoText = '';
filteredTodos: Todo[] = [];
currentFilter: AppState['todos']['filter'] = 'all';
filters: AppState['todos']['filter'][] = ['all', 'active', 'completed'];
private unsubscribe?: () => void;
constructor(private stateService: StateService) {}
ngOnInit(): void {
this.updateLocalState();
// Subscribe to state changes
this.unsubscribe = this.stateService.subscribe(() => {
this.updateLocalState();
});
}
ngOnDestroy(): void {
this.unsubscribe?.();
}
private updateLocalState(): void {
this.filteredTodos = this.stateService.getFilteredTodos();
this.currentFilter = this.stateService.getTodoFilter();
}
addTodo(): void {
if (this.newTodoText.trim()) {
this.stateService.addTodo(this.newTodoText.trim());
this.newTodoText = '';
}
}
toggleTodo(id: string): void {
this.stateService.toggleTodo(id);
}
deleteTodo(id: string): void {
this.stateService.deleteTodo(id);
}
setFilter(filter: AppState['todos']['filter']): void {
this.stateService.setTodoFilter(filter);
}
trackByTodoId(index: number, todo: Todo): string {
return todo.id;
}
}
📋 CHAPTER SUMMARY
🎯 Key Takeaways
Advanced Generic Programming Mastery
- Conditional Types: Master
T extends U ? X : Ypatterns for type-level branching logic - Mapped Types: Transform object types systematically with
{ [K in keyof T]: Transform<T[K]> } - Template Literal Types: Create dynamic string types for type-safe APIs and routing
- Utility Type Creation: Build reusable type transformations for common patterns
Critical Interview Points: - Explain how conditional types enable type-level computation - Demonstrate mapped type usage for API response transformation - Show template literal types for path parameter extraction - Create custom utility types that solve real Angular problems
Decorator Pattern Excellence
- Class Decorators: Enhance classes with metadata and behavior modification
- Method Decorators: Add cross-cutting concerns like caching, logging, validation
- Property Decorators: Enable reactive programming and data binding
- Parameter Decorators: Implement dependency injection and validation
Critical Interview Points: - Implement performance monitoring decorators for Angular services - Create validation decorators for form handling - Show how decorators enable aspect-oriented programming - Demonstrate real-world Angular applications of each decorator type
Type-Level Programming Expertise
- Recursive Types: Handle complex nested data structures with type safety
- Type Computations: Perform calculations and logic at compile time
- Parser Types: Validate string patterns and extract structured data
- Advanced Constraints: Create sophisticated type validation systems
Critical Interview Points: - Build recursive types for tree-like data structures - Implement type-level arithmetic and string manipulation - Create compile-time validation for configuration schemas - Show how type-level programming prevents runtime errors
Angular Integration Patterns
- Service Typing: Create type-safe service architectures with dependency injection
- Component Interfaces: Build flexible, reusable components with generic typing
- Form Validation: Implement sophisticated validation with cross-field constraints
- State Management: Design type-safe state systems with action validation
Critical Interview Points: - Demonstrate type-safe HTTP client implementations - Create generic components that maintain type safety - Build advanced form validation with TypeScript constraints - Show how TypeScript improves Angular application architecture
🎓 Interview Preparation Strategies
FAANG+ Companies (Meta, Google, Amazon, Netflix, Apple, Microsoft)
Technical Deep Dive Topics: 1. Type System Architecture - How TypeScript's structural typing differs from nominal typing - Type erasure and its implications for runtime behavior - Advanced inference algorithms and constraint solving
- Performance-Critical Patterns
- Lazy evaluation in type operations
- Compile-time optimization techniques
-
Memory-efficient type structures
-
System Design with TypeScript
- Large-scale application architecture
- Module federation and type sharing
- Cross-team type safety strategies
Sample Questions: - "Design a type-safe event system that scales to thousands of event types" - "Implement a compile-time SQL query builder with full type safety" - "Create a distributed state management system with TypeScript"
Preparation Focus: - Practice implementing complex type algorithms - Study TypeScript compiler internals - Build performance-critical type operations - Understand advanced constraint solving
Senior Level Companies (Stripe, Shopify, Atlassian, Uber)
Practical Implementation Focus: 1. Real-World Problem Solving - API client type safety - Form validation systems - Configuration management - Error handling patterns
- Architecture Patterns
- Module design with type constraints
- Plugin systems with type validation
- Cross-cutting concerns implementation
-
Testing strategies for typed code
-
Team Collaboration
- Type definition sharing strategies
- Code review best practices
- Migration strategies for large codebases
- Documentation patterns for complex types
Sample Questions: - "Build a type-safe API client that validates requests and responses" - "Design a plugin system with compile-time type validation" - "Implement a configuration system with environment-specific typing"
Preparation Focus: - Build complete, production-ready TypeScript applications - Practice explaining complex type patterns to non-experts - Study migration strategies from JavaScript to TypeScript - Focus on maintainable, scalable code architecture
Mid-Level Companies (Growing Startups, Series A-B)
Foundation Mastery: 1. Core Pattern Application - Builder, Observer, Strategy, Factory patterns with TypeScript - Basic generic programming - Interface design principles - Type guard implementation
- Angular Integration
- Service typing best practices
- Component interface design
- Form handling with type safety
-
State management fundamentals
-
Code Quality Focus
- Type-driven development practices
- Testing typed code effectively
- Refactoring JavaScript to TypeScript
- Common pitfall avoidance
Sample Questions: - "Implement a type-safe form validation system" - "Create a generic data table component" - "Build a simple state management solution with TypeScript"
Preparation Focus: - Master fundamental TypeScript patterns - Practice converting JavaScript code to TypeScript - Build small but complete Angular applications - Focus on clean, readable type definitions
📚 Recommended Practice Areas
Daily Practice (15-30 minutes)
- Type Challenges: Work through TypeScript type challenges on GitHub
- Code Reading: Study TypeScript source code in popular libraries
- Pattern Practice: Implement one design pattern with TypeScript daily
- Angular Integration: Build small Angular features using advanced patterns
Weekly Projects (2-4 hours)
- Mini Applications: Build complete features using chapter concepts
- Library Creation: Design reusable TypeScript libraries
- Migration Practice: Convert JavaScript projects to TypeScript
- Performance Analysis: Optimize type operations for large datasets
Monthly Deep Dives (4-8 hours)
- Complex Systems: Build sophisticated applications showcasing multiple patterns
- Open Source Contribution: Contribute to TypeScript or Angular projects
- Architecture Design: Design large-scale TypeScript application architectures
- Teaching Others: Explain complex concepts to solidify understanding
🔗 Next Steps and Continued Learning
Immediate Actions
- Practice Implementation: Complete all chapter exercises with variations
- Build Portfolio: Create showcase projects demonstrating mastery
- Study Real Codebases: Analyze production TypeScript applications
- Join Communities: Participate in TypeScript and Angular discussions
Advanced Topics to Explore
- TypeScript Compiler API: Build custom type checking tools
- Advanced Testing: Property-based testing with typed generators
- Performance Optimization: Advanced bundling and tree-shaking
- Language Design: Contribute to TypeScript language development
Career Development
- Technical Writing: Document and share advanced TypeScript patterns
- Conference Speaking: Present on TypeScript best practices
- Mentoring: Help others learn advanced TypeScript concepts
- Open Source Leadership: Lead TypeScript community projects
🎯 Final Interview Tips
Technical Demonstration
- Code Live: Be prepared to implement patterns from scratch
- Explain Reasoning: Articulate why you chose specific type approaches
- Show Alternatives: Discuss trade-offs between different implementations
- Handle Edge Cases: Consider error scenarios and type safety implications
Communication Excellence
- Use Precise Terminology: Demonstrate deep understanding with correct vocabulary
- Visual Explanations: Draw type relationships and data flow diagrams
- Real-World Context: Connect patterns to actual business problems
- Progressive Complexity: Start simple and build up to advanced concepts
Problem-Solving Approach
- Understand Requirements: Clarify type safety and performance needs
- Design Type Architecture: Plan type relationships before implementation
- Implement Incrementally: Build up complex patterns step by step
- Test and Validate: Verify type safety and runtime behavior
- Optimize and Refine: Improve performance and maintainability
Confidence Building
- Practice Under Pressure: Solve problems with time constraints
- Mock Interviews: Practice explaining complex concepts clearly
- Code Reviews: Seek feedback on TypeScript pattern implementations
- Teaching: Explain patterns to others to solidify understanding
Remember: Advanced TypeScript patterns are tools for solving real problems. Focus on understanding when and why to use each pattern, not just how to implement them. The best TypeScript developers combine technical mastery with practical judgment and clear communication.
Success Metrics: You've mastered this chapter when you can implement any pattern from memory, explain trade-offs clearly, and adapt patterns to solve novel problems in real-world Angular applications.
⚗️ TYPE-LEVEL PROGRAMMING
🎯 Recursive Types: Advanced Data Structures
Recursive types enable modeling of complex, nested data structures with full type safety.
💡 Foundation: Basic Recursive Types
// Simple recursive type for nested comments
interface Comment {
id: number;
text: string;
author: string;
replies?: Comment[]; // Recursive reference
}
// More complex recursive types for Angular
type NestedRoutes = {
path: string;
component?: any;
children?: NestedRoutes[];
data?: any;
};
// Type-safe route configuration
const routeConfig: NestedRoutes[] = [
{
path: 'users',
children: [
{ path: '', component: UserListComponent },
{
path: ':id',
component: UserDetailComponent,
children: [
{ path: 'profile', component: UserProfileComponent },
{ path: 'settings', component: UserSettingsComponent }
]
}
]
}
];
// Recursive form structure
type FormValue<T> = T extends Array<infer U>
? FormArray<FormValue<U>>
: T extends object
? FormGroup<{ [K in keyof T]: FormValue<T[K]> }>
: FormControl<T>;
// Nested validation rules
type ValidationRules<T> = T extends Array<infer U>
? { arrayRules?: any; itemRules?: ValidationRules<U> }
: T extends object
? { [K in keyof T]?: ValidationRules<T[K]> }
: { required?: boolean; custom?: (value: T) => boolean };
⚡ Advanced Recursive Patterns
// Deep readonly implementation
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends (infer U)[]
? ReadonlyArray<DeepReadonly<U>>
: T[P] extends Function
? T[P]
: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
// Deep partial for partial updates
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
? Array<DeepPartial<U>>
: T[P] extends Function
? T[P]
: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
// Path-based property access (like Lodash.get)
type PathKeys<T> = T extends Array<infer U>
? `${number}` | `${number}.${PathKeys<U>}`
: T extends object
? {
[K in keyof T]: K extends string | number
? T[K] extends object
? `${K}` | `${K}.${PathKeys<T[K]>}`
: `${K}`
: never;
}[keyof T]
: never;
type GetValueByPath<T, P extends string> = P extends `${infer K}.${infer Rest}`
? K extends keyof T
? GetValueByPath<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
// Type-safe property getter for Angular
class TypeSafeGetter {
static get<T, P extends PathKeys<T>>(
obj: T,
path: P
): GetValueByPath<T, P> | undefined {
const keys = (path as string).split('.');
let current: any = obj;
for (const key of keys) {
if (current == null) return undefined;
current = current[key];
}
return current;
}
}
// Usage in Angular component
interface UserProfile {
personal: {
name: string;
age: number;
contacts: {
emails: string[];
phones: { type: string; number: string }[];
};
};
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
}
@Component({
selector: 'app-profile',
template: '...'
})
export class ProfileComponent {
user!: UserProfile;
getUserEmail(): string | undefined {
// Fully type-safe path access
return TypeSafeGetter.get(this.user, 'personal.contacts.emails.0');
}
getFirstPhoneNumber(): string | undefined {
return TypeSafeGetter.get(this.user, 'personal.contacts.phones.0.number');
}
}
// Recursive tree operations
type TreeNode<T> = {
value: T;
children?: TreeNode<T>[];
};
type FlattenTree<T> = T extends TreeNode<infer U>
? U | (T['children'] extends Array<infer V> ? FlattenTree<V> : never)
: never;
// Tree manipulation utilities
class TreeUtils {
static flatten<T>(tree: TreeNode<T>[]): Array<T> {
const result: T[] = [];
function traverse(nodes: TreeNode<T>[]): void {
for (const node of nodes) {
result.push(node.value);
if (node.children) {
traverse(node.children);
}
}
}
traverse(tree);
return result;
}
static find<T>(
tree: TreeNode<T>[],
predicate: (value: T) => boolean
): T | undefined {
for (const node of tree) {
if (predicate(node.value)) {
return node.value;
}
if (node.children) {
const found = this.find(node.children, predicate);
if (found) return found;
}
}
return undefined;
}
}
🧮 Type-Level Computations
Performing calculations and logic at the type level for compile-time guarantees.
💡 Foundation: Type-Level Mathematics
// Tuple length calculation
type Length<T extends readonly any[]> = T['length'];
// Addition at type level
type Add<A extends number, B extends number> =
[...Array<A>, ...Array<B>]['length'];
// Subtraction using tuple manipulation
type Subtract<A extends number, B extends number> =
Array<A> extends [...Array<B>, ...infer Rest]
? Rest['length']
: never;
// Range generation
type Range<N extends number, Result extends any[] = []> =
Result['length'] extends N
? Result
: Range<N, [...Result, Result['length']]>;
type ZeroToFive = Range<6>; // [0, 1, 2, 3, 4, 5]
// Comparison operations
type GreaterThan<A extends number, B extends number> =
Array<A> extends [...Array<B>, ...any[]] ? true : false;
type LessThan<A extends number, B extends number> =
Array<B> extends [...Array<A>, ...any[]] ? true : false;
// Array operations at type level
type Head<T extends readonly any[]> = T extends readonly [infer H, ...any[]] ? H : never;
type Tail<T extends readonly any[]> = T extends readonly [any, ...infer T] ? T : never;
type Last<T extends readonly any[]> = T extends readonly [...any[], infer L] ? L : never;
// Type-safe array index validation
type ValidIndex<T extends readonly any[], I extends number> =
I extends keyof T ? I : never;
type SafeArrayAccess<T extends readonly any[], I extends number> =
I extends ValidIndex<T, I> ? T[I] : never;
// Usage in Angular for type-safe array operations
class SafeArray<T extends readonly any[]> {
constructor(private array: T) {}
get<I extends number>(index: I): SafeArrayAccess<T, I> {
if (index >= 0 && index < this.array.length) {
return this.array[index] as SafeArrayAccess<T, I>;
}
throw new Error(`Index ${index} out of bounds`);
}
head(): Head<T> {
return this.array[0] as Head<T>;
}
tail(): Tail<T> {
return this.array.slice(1) as any;
}
last(): Last<T> {
return this.array[this.array.length - 1] as Last<T>;
}
}
// Angular service using type-level computations
@Injectable()
export class ConfigurationService {
private readonly environments = ['development', 'staging', 'production'] as const;
getEnvironment<I extends ValidIndex<typeof this.environments, I>>(
index: I
): typeof this.environments[I] {
return this.environments[index];
}
// Type-safe configuration based on environment
getConfig<E extends typeof this.environments[number]>(
env: E
): E extends 'development'
? { debug: true; apiUrl: string }
: E extends 'staging'
? { debug: false; apiUrl: string; staging: true }
: { debug: false; apiUrl: string; production: true } {
const configs = {
development: { debug: true, apiUrl: 'http://localhost:3000' },
staging: { debug: false, apiUrl: 'https://staging-api.example.com', staging: true },
production: { debug: false, apiUrl: 'https://api.example.com', production: true }
};
return configs[env] as any;
}
}
⚡ Advanced Type-Level Algorithms
// String manipulation at type level
type Split<S extends string, D extends string> =
S extends `${infer T}${D}${infer U}`
? [T, ...Split<U, D>]
: [S];
type Join<T extends readonly string[], D extends string> =
T extends readonly [infer F, ...infer R]
? F extends string
? R extends readonly string[]
? R['length'] extends 0
? F
: `${F}${D}${Join<R, D>}`
: never
: never
: '';
// Reverse string/array at type level
type Reverse<T extends readonly any[]> =
T extends readonly [...infer Rest, infer Last]
? [Last, ...Reverse<Rest>]
: [];
// Type-level sorting (for constant arrays)
type Sort<T extends readonly number[]> =
T extends readonly [infer First, ...infer Rest]
? First extends number
? Rest extends readonly number[]
? Insert<First, Sort<Rest>>
: [First]
: never
: [];
type Insert<N extends number, T extends readonly number[]> =
T extends readonly [infer First, ...infer Rest]
? First extends number
? Rest extends readonly number[]
? LessThan<N, First> extends true
? [N, ...T]
: [First, ...Insert<N, Rest>]
: [N, First]
: [N]
: [N];
// Object manipulation at type level
type Merge<T, U> = {
[K in keyof T | keyof U]: K extends keyof U
? U[K]
: K extends keyof T
? T[K]
: never;
};
type DeepMerge<T, U> = {
[K in keyof T | keyof U]: K extends keyof U
? K extends keyof T
? T[K] extends object
? U[K] extends object
? DeepMerge<T[K], U[K]>
: U[K]
: U[K]
: U[K]
: K extends keyof T
? T[K]
: never;
};
// Advanced object key manipulation
type CamelCase<S extends string> =
S extends `${infer P1}_${infer P2}${infer P3}`
? `${P1}${Uppercase<P2>}${CamelCase<P3>}`
: S;
type CamelCaseKeys<T> = {
[K in keyof T as CamelCase<K & string>]: T[K] extends object
? CamelCaseKeys<T[K]>
: T[K];
};
// API response transformation at type level
type SnakeCase<S extends string> =
S extends `${infer P1}${infer P2}`
? P2 extends Uncapitalize<P2>
? `${Lowercase<P1>}${SnakeCase<P2>}`
: `${Lowercase<P1>}_${SnakeCase<Uncapitalize<P2>>}`
: S;
type SnakeCaseKeys<T> = {
[K in keyof T as SnakeCase<K & string>]: T[K] extends object
? SnakeCaseKeys<T[K]>
: T[K];
};
// Angular HTTP interceptor with automatic case conversion
@Injectable()
export class CaseConversionInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Convert request body to snake_case
let transformedReq = req;
if (req.body && req.method !== 'GET') {
const snakeCaseBody = this.toSnakeCase(req.body);
transformedReq = req.clone({ body: snakeCaseBody });
}
return next.handle(transformedReq).pipe(
map(event => {
if (event instanceof HttpResponse) {
// Convert response body to camelCase
const camelCaseBody = this.toCamelCase(event.body);
return event.clone({ body: camelCaseBody });
}
return event;
})
);
}
private toSnakeCase(obj: any): any {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(item => this.toSnakeCase(item));
const result: any = {};
for (const [key, value] of Object.entries(obj)) {
const snakeKey = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
result[snakeKey] = this.toSnakeCase(value);
}
return result;
}
private toCamelCase(obj: any): any {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(item => this.toCamelCase(item));
const result: any = {};
for (const [key, value] of Object.entries(obj)) {
const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
result[camelKey] = this.toCamelCase(value);
}
return result;
}
}
📝 Parser Types: Compile-Time Validation
Creating types that parse and validate string patterns at compile time.
💡 Advanced Parser Patterns
// URL parser at type level
type ParseUrl<T extends string> =
T extends `${infer Protocol}://${infer Rest}`
? {
protocol: Protocol;
host: ParseHost<Rest>;
path: ParsePath<Rest>;
query: ParseQuery<Rest>;
}
: never;
type ParseHost<T extends string> =
T extends `${infer Host}/${infer _}`
? Host
: T extends `${infer Host}?${infer _}`
? Host
: T;
type ParsePath<T extends string> =
T extends `${infer _}/${infer Path}`
? Path extends `${infer PathPart}?${infer _}`
? PathPart
: Path
: '';
type ParseQuery<T extends string> =
T extends `${infer _}?${infer Query}`
? ParseQueryParams<Query>
: {};
type ParseQueryParams<T extends string> =
T extends `${infer Param}&${infer Rest}`
? ParseParam<Param> & ParseQueryParams<Rest>
: ParseParam<T>;
type ParseParam<T extends string> =
T extends `${infer Key}=${infer Value}`
? { [K in Key]: Value }
: {};
// Usage
type ExampleUrl = ParseUrl<'https://api.example.com/users/123?include=profile&format=json'>;
// Result: {
// protocol: 'https';
// host: 'api.example.com';
// path: 'users/123';
// query: { include: 'profile'; format: 'json' };
// }
// SQL-like query parser
type ParseSelect<T extends string> =
T extends `SELECT ${infer Columns} FROM ${infer Table}${infer Rest}`
? {
type: 'SELECT';
columns: ParseColumns<Columns>;
table: Table;
where: ParseWhere<Rest>;
}
: never;
type ParseColumns<T extends string> =
T extends `${infer Column}, ${infer Rest}`
? [Column, ...ParseColumns<Rest>]
: [T];
type ParseWhere<T extends string> =
T extends ` WHERE ${infer Condition}`
? ParseCondition<Condition>
: null;
type ParseCondition<T extends string> =
T extends `${infer Field} = ${infer Value}`
? { field: Field; operator: '='; value: Value }
: T extends `${infer Field} > ${infer Value}`
? { field: Field; operator: '>'; value: Value }
: never;
// CSS selector parser
type ParseSelector<T extends string> =
T extends `.${infer Class}`
? { type: 'class'; name: Class }
: T extends `#${infer Id}`
? { type: 'id'; name: Id }
: T extends `${infer Tag}.${infer Class}`
? { type: 'tag-class'; tag: Tag; class: Class }
: T extends `${infer Tag}#${infer Id}`
? { type: 'tag-id'; tag: Tag; id: Id }
: { type: 'tag'; name: T };
// Type-safe CSS-in-JS for Angular
type CSSSelector =
| `.${string}` // Class selector
| `#${string}` // ID selector
| string; // Tag selector
type CSSRule<T extends CSSSelector> = {
selector: T;
styles: CSSProperties;
};
class TypedStylesheet {
private rules: CSSRule<any>[] = [];
addRule<T extends CSSSelector>(
selector: T,
styles: CSSProperties
): TypedStylesheet {
this.rules.push({ selector, styles });
return this;
}
generate(): string {
return this.rules
.map(rule => `${rule.selector} { ${this.generateStyles(rule.styles)} }`)
.join('\n');
}
private generateStyles(styles: CSSProperties): string {
return Object.entries(styles)
.map(([prop, value]) => `${this.kebabCase(prop)}: ${value};`)
.join(' ');
}
private kebabCase(str: string): string {
return str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
}
}
// Angular directive using typed styles
@Directive({
selector: '[appTypedStyles]'
})
export class TypedStylesDirective implements OnInit {
@Input() theme: 'light' | 'dark' = 'light';
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit(): void {
const stylesheet = new TypedStylesheet()
.addRule('.button', {
padding: '10px 20px',
borderRadius: '4px',
border: 'none'
})
.addRule('.button.primary', {
backgroundColor: this.theme === 'light' ? '#007bff' : '#0056b3',
color: 'white'
})
.addRule('#main-container', {
maxWidth: '1200px',
margin: '0 auto'
});
const styleElement = this.renderer.createElement('style');
this.renderer.appendChild(styleElement,
this.renderer.createText(stylesheet.generate())
);
this.renderer.appendChild(document.head, styleElement);
}
}
// Advanced validation parser for forms
type ParseValidationRule<T extends string> =
T extends `required`
? { type: 'required' }
: T extends `minLength:${infer N}`
? { type: 'minLength'; value: N }
: T extends `maxLength:${infer N}`
? { type: 'maxLength'; value: N }
: T extends `pattern:${infer P}`
? { type: 'pattern'; value: P }
: T extends `email`
? { type: 'email' }
: never;
type ParseValidationRules<T extends string> =
T extends `${infer Rule}|${infer Rest}`
? [ParseValidationRule<Rule>, ...ParseValidationRules<Rest>]
: [ParseValidationRule<T>];
// Type-safe form validation
interface ValidationConfig<T extends string = string> {
rules: T;
message?: string;
}
class TypedFormValidator {
static createValidator<T extends string>(
config: ValidationConfig<T>
): ValidatorFn {
const rules = config.rules.split('|') as T[];
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value;
const errors: ValidationErrors = {};
for (const rule of rules) {
if (rule === 'required' && (!value || value.trim() === '')) {
errors['required'] = true;
} else if (rule.startsWith('minLength:')) {
const minLength = parseInt(rule.split(':')[1]);
if (value && value.length < minLength) {
errors['minLength'] = { requiredLength: minLength, actualLength: value.length };
}
} else if (rule.startsWith('maxLength:')) {
const maxLength = parseInt(rule.split(':')[1]);
if (value && value.length > maxLength) {
errors['maxLength'] = { requiredLength: maxLength, actualLength: value.length };
}
} else if (rule === 'email') {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (value && !emailRegex.test(value)) {
errors['email'] = true;
}
}
}
return Object.keys(errors).length > 0 ? errors : null;
};
}
}
// Usage in Angular reactive forms
@Component({
selector: 'app-registration',
template: '...'
})
export class RegistrationComponent implements OnInit {
registrationForm!: FormGroup;
ngOnInit(): void {
this.registrationForm = new FormGroup({
email: new FormControl('', [
TypedFormValidator.createValidator({
rules: 'required|email',
message: 'Please enter a valid email address'
})
]),
password: new FormControl('', [
TypedFormValidator.createValidator({
rules: 'required|minLength:8|maxLength:50',
message: 'Password must be between 8 and 50 characters'
})
]),
username: new FormControl('', [
TypedFormValidator.createValidator({
rules: 'required|minLength:3|maxLength:20|pattern:^[a-zA-Z0-9_]+$',
message: 'Username must be 3-20 characters, alphanumeric and underscores only'
})
])
});
}
}