What are Decorators in Angular?
Question 1: What are Decorators in Angular and why are they important?
Answer: Decorators are special declarations that modify classes, methods, properties, or parameters at design time. They are crucial in Angular for:
- Configuring components, services, and modules
- Implementing dependency injection
- Managing metadata
- Defining component behavior and structure
Example of common decorators:
// Class decorators
@Component({
selector: 'app-root',
template: `<h1>{{title}}</h1>`
})
class AppComponent {
title = 'My App';
}
@Injectable({
providedIn: 'root'
})
class UserService {
getUsers() { }
}
// Property decorators
class UserComponent {
@Input() user: User;
@Output() save = new EventEmitter<User>();
@ViewChild('content') content: ElementRef;
@HostBinding('class.active') isActive = false;
}
// Method decorators
class Component {
@HostListener('click')
onClick() { }
@ViewChildren(ChildComponent)
children: QueryList<ChildComponent>;
}
// Parameter decorators
class Component {
constructor(
// Basic service injection
private service: DataService,
// Token-based injection
@Inject(API_CONFIG) private config: ApiConfig,
// Optional dependency
@Optional() private logger: LoggerService,
// Skip self in injection chain
@SkipSelf() private parent: ParentService,
// Host-level injection
@Host() private host: HostService,
// Attribute value injection
@Attribute('role') private role: string
) { }
@Input() set data(value: any) {
if (this.logger) {
this.logger.log('Data updated:', value);
}
this._data = value;
}
private _data: any;
}
Question 2: How do Class Decorators work in Angular?
Answer: Class decorators define the metadata for classes, such as components, services, and modules. They determine how these classes should be processed, instantiated, and used.
// Component decorator
@Component({
selector: 'app-user',
template: `
<div class="user-card">
<h2>{{user.name}}</h2>
<p>{{user.email}}</p>
<ng-content></ng-content>
</div>
`,
styles: [`
.user-card {
padding: 1rem;
border: 1px solid #ccc;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [UserService]
})
class UserComponent implements OnInit {
@Input() user: User;
constructor(private service: UserService) {}
ngOnInit() {
// Lifecycle hook
}
}
// Injectable decorator
@Injectable({
providedIn: 'root',
useFactory: (http: HttpClient) => {
return new UserService(http);
},
deps: [HttpClient]
})
class UserService {
constructor(private http: HttpClient) {}
}
// NgModule decorator
@NgModule({
declarations: [UserComponent],
imports: [CommonModule],
exports: [UserComponent],
providers: [
{
provide: USER_CONFIG,
useValue: defaultConfig
}
]
})
class UserModule { }
Question 3: How do Property and Method Decorators work?
Answer: Property and method decorators modify or enhance class members. They’re used for data binding, event handling, and accessing child elements.
@Component({
template: `
<div [class.active]="isActive">
<button (click)="onClick()">
{{buttonText}}
</button>
<div #content>
<ng-content></ng-content>
</div>
</div>
`
})
class InteractiveComponent {
// Property decorators
@Input() buttonText = 'Click me';
@Output() action = new EventEmitter<void>();
@ViewChild('content') content: ElementRef;
@HostBinding('attr.role') role = 'button';
// Method decorators
@HostListener('click')
onClick() {
this.action.emit();
}
@HostListener('window:resize', ['$event'])
onResize(event: Event) {
this.handleResize(event);
}
// Lifecycle hooks
@Input() set config(value: Config) {
// Setter decorator
this._config = value;
this.updateConfig();
}
private _config: Config;
}
Question 4: How do Parameter Decorators work with Dependency Injection?
Answer: Parameter decorators are used in constructor parameters to modify how dependencies are injected, allowing for custom injection behavior and optional dependencies.
// Custom injection token
const API_CONFIG = new InjectionToken<ApiConfig>('api.config');
@Component({
template: `<div>{{data | json}}</div>`
})
class DataComponent {
constructor(
// Basic service injection
private service: DataService,
// Token-based injection
@Inject(API_CONFIG) private config: ApiConfig,
// Optional dependency
@Optional() private logger: LoggerService,
// Skip self in injection chain
@SkipSelf() private parent: ParentService,
// Host-level injection
@Host() private host: HostService,
// Attribute value injection
@Attribute('role') private role: string
) { }
@Input() set data(value: any) {
if (this.logger) {
this.logger.log('Data updated:', value);
}
this._data = value;
}
private _data: any;
}
Question 5: How do you create Custom Decorators?
Answer: Custom decorators can be created to add reusable behavior or metadata to classes, properties, methods, or parameters.
// Custom class decorator
function Logger() {
return function (target: any) {
// Add logging functionality
const original = target.prototype.ngOnInit;
target.prototype.ngOnInit = function () {
console.log(`${target.name} initialized`);
original?.apply(this);
};
return target;
};
}
// Custom property decorator
function Debounce(delay: number = 300) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
let timeout: any;
descriptor.value = function (...args: any[]) {
clearTimeout(timeout);
timeout = setTimeout(() => {
original.apply(this, args);
}, delay);
};
return descriptor;
};
}
// Using custom decorators
@Logger()
@Component({
template: `
<input (input)="onSearch($event)">
`
})
class SearchComponent {
@Debounce(500)
onSearch(event: Event) {
// Search logic
}
}
Common Interview Follow-up Questions:
Q: What’s the difference between @Injectable() and @Component()? A: @Injectable() marks a class as available for dependency injection, while @Component() defines a UI component with its template and styles:
@Injectable({ providedIn: 'root' // Singleton service }) class GlobalService { } @Component({ selector: 'app-root', template: `...`, // UI template styles: [`...`] // Component styles }) class AppComponent { }
Q: How do you handle decorator inheritance? A: Decorators are inherited but can be overridden in child classes:
@Component({ selector: 'base-component', template: '<div>Base</div>' }) class BaseComponent { @Input() data: any; } @Component({ selector: 'child-component', template: '<div>Child</div>' }) class ChildComponent extends BaseComponent { // Inherits @Input() data @Input() override data: string; // Type narrowing }
Q: What are the performance implications of decorators? A: Decorators are processed at runtime, so complex decorators can impact performance:
// Efficient decorator function Simple() { return function (target: any) { // Simple metadata addition return target; }; } // Potentially expensive decorator function Complex() { return function (target: any) { // Deep object manipulation return new Proxy(target, { // Complex handlers }); }; }
Q: How do you test components with decorators? A: Use TestBed to properly configure decorators in tests:
describe('DecoratedComponent', () => { let component: DecoratedComponent; let fixture: ComponentFixture<DecoratedComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [DecoratedComponent], providers: [ { provide: API_CONFIG, useValue: mockConfig } ] }).compileComponents(); fixture = TestBed.createComponent(DecoratedComponent); component = fixture.componentInstance; }); it('should handle decorated properties', () => { component.decoratedMethod(); expect(/* assertions */); }); });