Introduction
Angular decorators are special functions that modify TypeScript classes, properties, methods, or parameters by adding metadata to enhance their functionality. They are prefixed with the @
symbol and are a fundamental part of Angular’s architecture. Decorators play a critical role in Angular development by enabling dependency injection, component definition, property binding, and many other core features without complex inheritance hierarchies.
Core Types of Angular Decorators
Category | Purpose | Examples |
---|---|---|
Class Decorators | Define Angular artifacts like components, modules, services | @Component , @NgModule , @Injectable |
Property Decorators | Work with properties of classes | @Input , @Output , @ViewChild |
Method Decorators | Enhance class methods | @HostListener , @ContentChild |
Parameter Decorators | Used for function arguments | @Inject , @Optional , @Self |
Class Decorators
@Component
Defines an Angular component.
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css'],
providers: [ExampleService],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.Emulated
})
export class ExampleComponent { }
@NgModule
Defines an Angular module.
@NgModule({
declarations: [ExampleComponent],
imports: [CommonModule, FormsModule],
exports: [ExampleComponent],
providers: [ExampleService],
bootstrap: [AppComponent]
})
export class ExampleModule { }
@Injectable
Marks a class as available for dependency injection.
@Injectable({
providedIn: 'root' // Application-wide singleton
})
export class ExampleService { }
// Scoped to a specific module
@Injectable({
providedIn: ExampleModule
})
export class ScopedService { }
@Directive
Creates a reusable directive.
@Directive({
selector: '[appHighlight]',
exportAs: 'appHighlight',
host: { '(mouseenter)': 'onMouseEnter()' }
})
export class HighlightDirective { }
@Pipe
Defines a data transformation pipe.
@Pipe({
name: 'formatDate',
pure: true // Default is true (stateless)
})
export class FormatDatePipe implements PipeTransform {
transform(value: any, ...args: any[]): any { }
}
Property Decorators
@Input
Defines an input property for a component.
@Input() itemData: any;
@Input('aliasName') itemData: any; // With alias
// With transform logic
@Input()
set itemData(value: any) {
this._itemData = this.transformValue(value);
}
get itemData(): any {
return this._itemData;
}
@Output
Defines an output event emitter.
@Output() itemSelected = new EventEmitter<any>();
@Output('selectAlias') itemSelected = new EventEmitter<any>(); // With alias
@ViewChild
Provides access to a child component, directive, or DOM element.
// Access component
@ViewChild(ChildComponent) childComp: ChildComponent;
// Access directive with static query (available in ngOnInit)
@ViewChild(ExampleDirective, { static: true }) directive: ExampleDirective;
// Access element reference
@ViewChild('inputRef') inputElement: ElementRef;
// With read option to get different token from the same element
@ViewChild('inputRef', { read: ViewContainerRef }) container: ViewContainerRef;
@ViewChildren
Similar to @ViewChild but returns all matching elements as a QueryList.
@ViewChildren(ChildComponent) childComps: QueryList<ChildComponent>;
@ViewChildren('itemRef') items: QueryList<ElementRef>;
@ContentChild & @ContentChildren
Access projected content.
@ContentChild(ChildComponent) projectedChild: ChildComponent;
@ContentChildren(ChildComponent) projectedChildren: QueryList<ChildComponent>;
@HostBinding
Binds a host element property to a component property.
@HostBinding('class.active') isActive = false;
@HostBinding('style.color') color = 'blue';
@HostBinding('attr.role') role = 'button';
Method Decorators
@HostListener
Listens for host element events.
@HostListener('click', ['$event'])
onClick(event: MouseEvent) {
// Handle click event
}
@HostListener('window:resize', ['$event'])
onResize(event: UIEvent) {
// Handle window resize
}
@ViewChild with methods
Accessing methods on child components.
@ViewChild('child') childComponent: ChildComponent;
callChildMethod() {
this.childComponent.childMethod();
}
Parameter Decorators
@Inject
Manually specifies a dependency to inject.
constructor(@Inject(DOCUMENT) private document: Document) { }
@Optional
Makes a dependency optional.
constructor(@Optional() private logger: LoggerService) { }
@Self
Limits provider lookup to the current component’s injector.
constructor(@Self() private service: ExampleService) { }
@SkipSelf
Starts dependency lookup from the parent injector.
constructor(@SkipSelf() private parentService: ParentService) { }
@Host
Limits provider lookup to the host component.
constructor(@Host() private hostService: HostService) { }
@Attribute
Injects an attribute value from the host element.
constructor(@Attribute('role') public role: string) { }
Custom Decorators
You can create your own decorators for specialized use cases:
// Simple property decorator
function Capitalize() {
return function(target: any, key: string) {
let value = target[key];
const getter = () => value;
const setter = (next: string) => {
value = next.charAt(0).toUpperCase() + next.slice(1);
};
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
// Usage
export class ExampleComponent {
@Capitalize() title: string = 'example'; // Will become 'Example'
}
Common Challenges and Solutions
Challenge | Solution |
---|---|
Circular dependencies | Use forwardRef(): constructor(@Inject(forwardRef(() => DependentService)) private service: DependentService) |
Dynamic component loading | Use ComponentFactoryResolver with @ViewChild(ViewContainerRef) |
Content projection issues | Use @ContentChild with ngAfterContentInit instead of ngOnInit |
Change detection problems | Use ChangeDetectorRef.markForCheck() when using OnPush strategy |
Property initialization timing | Use *ngIf with the as syntax: *ngIf="item as selectedItem" |
Best Practices
Component Input/Output Naming: Use descriptive names that clearly indicate purpose.
Metadata Consolidation: Group related decorators:
@Component({...}) export class DashboardComponent { // Group related inputs @Input() userData: User; @Input() userPreferences: Preferences; // Group related outputs @Output() save = new EventEmitter<User>(); @Output() cancel = new EventEmitter<void>(); }
Directive Selectors: Use camelCase with brackets for attributes, kebab-case for elements:
@Directive({ selector: '[appHighlight]' }) // Attribute directive @Component({ selector: 'app-user-card' }) // Element directive
Query Timing:
- Use
{static: true}
when you need access in ngOnInit - Use
{static: false}
(default) when targeting elements inside *ngIf or *ngFor
- Use
Performance Considerations:
- Limit the number of @HostListeners for frequently fired events
- Use pure pipes rather than methods in templates
- Implement OnPush change detection where appropriate
Advanced Decorator Patterns
Combining Decorators
@Component({...})
export class UserProfileComponent {
@Input() @HostBinding('attr.user-id') userId: string;
@HostListener('click')
@LogMethod() // Custom decorator
handleClick() {
// ...
}
}
Feature Flags with Decorators
function FeatureFlag(flagName: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
if (featureService.isEnabled(flagName)) {
return originalMethod.apply(this, args);
}
console.warn(`Feature ${flagName} is disabled`);
return null;
};
return descriptor;
};
}
// Usage
@Component({...})
class ExampleComponent {
@FeatureFlag('new-dashboard')
showNewDashboard() {
// Only runs if feature flag is enabled
}
}
Resources for Further Learning
- Official Documentation: Angular.io Decorators
- GitHub Examples: Angular Decorators Samples
- Books:
- “Angular Development with TypeScript” by Yakov Fain and Anton Moiseev
- “Pro Angular” by Adam Freeman
- Courses:
- Angular University’s “Angular Core Deep Dive”
- Pluralsight’s “Angular Architecture and Best Practices”
- Tools:
- TypeScript Playground for testing custom decorators
- Angular Schematics for generating decorated classes
Decorator Evolution in Angular Versions
Version | Key Changes |
---|---|
Angular 9 | Ivy compiler: more efficient decorator processing |
Angular 10 | Stricter type checking for @Input() decorators |
Angular 13 | Improved inheritance of decorators in derived classes |
Angular 14 | Typed forms and improved decorator inference |
Angular 15 | Directive composition API with hostDirectives |
Angular 16 | Self-closing tags in component templates, improved signals |
Angular 17 | New control flow syntax, deferrable views |
This cheatsheet provides a comprehensive reference for Angular decorators, from basic usage to advanced patterns. As Angular continues to evolve, the decorator pattern remains central to its architecture, making this knowledge essential for effective Angular development.