Angular

Decorators

15 Questions

Decorators are functions that modify classes, properties, methods, or parameters. Angular uses them extensively to add metadata to TypeScript classes. They are prefixed with @ symbol. Decorators tell Angular how to process and configure a class, making them fundamental to the framework's architecture.
// Class decorators: @Component, @Directive, @Injectable, @NgModule, @Pipe
// Property decorators: @Input, @Output, @HostBinding, @ContentChild, @ViewChild
// Method decorators: @HostListener
// Parameter decorators: @Inject, @Optional, @Self, @SkipSelf

@Component({ selector: 'app-root', template: '<h1>Hello</h1>' })
export class AppComponent {}
Without decorators, Angular cannot identify what role a class plays. Class decorators define the type, while property decorators configure data flow.

@Input() marks a property as bindable from a parent component's template. The parent passes data down using property binding with square brackets. In Angular 16+, you can mark inputs as required using the required option. Inputs can also have transform functions and alias names.
// Child
@Input() name: string = '';
@Input({ required: true }) id!: number; // Required in Angular 16+

// Parent template
<app-child [name]="userName" [id]="userId"></app-child>
Input values are available in ngOnInit and later. Use ngOnChanges to react to input value changes over time.

Why it matters: Tests understanding of parent-to-child communication. Shows you can accept and react to configuration data.

Real applications: Passing user data, configuration settings, feature flags, and display options to child components.

Common mistakes: Developers use Inputs for non-readonly data causing confusion. They don't use ngOnChanges when they need to react to changes. They don't make Inputs truly inputs (should not modify them).

@Output() marks a property as an event emitter that sends data from child to parent. The property must be an EventEmitter instance. The parent listens using event binding with parentheses. The emitted value is accessible via $event in the template.
@Output() itemSelected = new EventEmitter<Item>();

onSelect(item: Item) {
  this.itemSelected.emit(item);
}

// Parent template
<app-child (itemSelected)="onItemSelect($event)"></app-child>
@Output enables child-to-parent communication. Use descriptive event names and emit only the data the parent needs.

Why it matters: Tests understanding of child-to-parent communication patterns. Shows you can create reusable components with custom events.

Real applications: Button clicks, form submissions, item selections, deletion confirmations. Any time child needs to notify parent of events.

Common mistakes: Developers emit entire objects when only a subset is needed. They use poor event names like 'change' instead of 'itemSelected'. They forget decorators are needed for event bindings to work.

@ViewChild queries for a child element, directive, or component defined in the component's own template. It provides a reference to the element allowing direct access to its properties and methods. The reference is available in ngAfterViewInit lifecycle hook. Use a template reference variable (#ref) or component type to query.
@ViewChild('inputRef') input!: ElementRef;
@ViewChild(ChildComponent) child!: ChildComponent;

ngAfterViewInit() {
  this.input.nativeElement.focus();
  this.child.doSomething();
}
Set static: true if you need the reference in ngOnInit instead of ngAfterViewInit. Use @ViewChildren to query for multiple elements.

Why it matters: Tests understanding of accessing DOM elements and child components directly. Shows you know when direct access is appropriate.

Real applications: Focus management on input fields, triggering child methods, direct DOM manipulation when needed, accessing chart libraries or third-party components.

Common mistakes: Developers access @ViewChild in ngOnInit (not available yet). They don't use typed templates coercion. They overuse direct access when data binding would work better.

@ContentChild queries for projected content that is placed between the component's tags using ng-content. Unlike @ViewChild which queries the component's own template, @ContentChild queries elements that the parent projected into the component. The reference is available in ngAfterContentInit lifecycle hook.
// Content projection
<app-card>
  <app-header>Title</app-header>
</app-card>

// In CardComponent
@ContentChild(HeaderComponent) header!: HeaderComponent;
Use @ContentChildren to query for multiple projected elements. Both return a QueryList that can be observed for changes.

Why it matters: Tests understanding of content projection and component composition. Shows you can build flexible wrapper components.

Real applications: Card components with projected headers/footers, modal dialogs with projected content, accordion components with projected panels.

Common mistakes: Developers confuse @ContentChild with @ViewChild. They don't understand content projection timing (available in ngAfterContentInit). They query wrong selector or component type.

@HostListener listens to events on the host element of the directive or component. It takes the event name as the first argument and an optional array of arguments. You can listen to DOM events like click, mouseenter, or global events like window:resize. It replaces manually adding event listeners in the constructor.
@HostListener('click', ['$event'])
onClick(event: MouseEvent) {
  console.log('Host clicked!', event);
}

@HostListener('window:resize', ['$event'])
onResize(event: Event) {
  this.windowWidth = window.innerWidth;
}
In modern Angular, prefer the host property in the decorator metadata over @HostListener. It provides the same functionality with less boilerplate.

@HostBinding binds a class property to a property of the host element. It can bind to classes, styles, attributes, or any DOM property. The binding is automatically updated when the property value changes. This is commonly used in directives to modify the host element's appearance.
@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
  @HostBinding('class.active') isActive = false;
  @HostBinding('style.backgroundColor') bgColor = '';

  @HostListener('mouseenter') onHover() {
    this.bgColor = 'yellow';
    this.isActive = true;
  }
}
Combine @HostBinding with @HostListener to create interactive directives. The host property in the decorator metadata is the modern alternative.

Why it matters: Tests understanding of directive styling and behavior. Shows you can create reusable interactive directives.

Real applications: Highlight directives on hover, disable state styling, dynamic theme application, accessibility features like focus indicators.

Common mistakes: Developers don't pair @HostBinding with @HostListener. They use inline styles instead of class binding. They forget @HostBinding is for properties, not template references.

@Injectable marks a class as available for dependency injection. The providedIn option specifies where the service is available. Using providedIn: 'root' creates a tree-shakable singleton available application-wide. Without this decorator, Angular cannot inject dependencies into the class constructor.
@Injectable({ providedIn: 'root' }) // Tree-shakable singleton
export class DataService {
  constructor(private http: HttpClient) {}
}
The providedIn: 'root' option eliminates the need to add the service to a providers array. Angular automatically removes unused services during tree-shaking.

Why it matters: Tests understanding of dependency injection and service registration. Shows you can make services available throughout the app.

Real applications: Data services, HTTP clients, logging services, authentication services. Almost every service needs @Injectable with providedIn: 'root'.

Common mistakes: Developers forget @Injectable decorator entirely. They use @Injectable without providedIn. They add to providers array when providedIn is cleaner.

@Inject explicitly specifies the injection token used for dependency injection. It is required for non-class tokens like strings, numbers, or InjectionToken instances. Without @Inject, Angular uses the type annotation to determine the token. Use InjectionToken for type-safe non-class values.
constructor(@Inject(API_URL) private apiUrl: string) {}

// With InjectionToken
export const API_URL = new InjectionToken<string>('api-url');
InjectionToken prevents naming collisions between providers. Always use InjectionToken instead of plain strings for better type safety and refactoring support.

Why it matters: Tests understanding of advanced dependency injection. Shows you know how to handle special injection tokens.

Real applications: Configuration values, API keys, feature flags, application-wide settings that must be injected as tokens.

Common mistakes: Developers use plain strings as injection tokens losing type safety. They don't create InjectionToken for typed non-class values. They forget @Inject when using non-class tokens.

@ViewChildren queries elements defined in the component's own template. @ContentChildren queries elements projected via ng-content from the parent. Both return a QueryList that can be observed for changes. @ViewChildren is available in ngAfterViewInit, @ContentChildren in ngAfterContentInit.
// ViewChildren: queries own template elements
@ViewChildren(ItemComponent) items!: QueryList<ItemComponent>;

// ContentChildren: queries projected elements
@ContentChildren(TabComponent) tabs!: QueryList<TabComponent>;

ngAfterViewInit() {
  this.items.changes.subscribe(() => console.log('Items changed'));
}
Use the changes Observable on QueryList to react to dynamic additions or removals. Both decorators support the descendants option for deep querying.

Why it matters: Tests understanding of querying multiple elements dynamically. Shows you can handle collections of components/elements.

Real applications: Tab components with multiple tab panels, lists with dynamic items, form arrays, menu items that change over time.

Common mistakes: Developers use @ViewChild/@ContentChild when they need @ViewChildren/@ContentChildren. They don't subscribe to .changes for dynamic lists. They iterate QueryList thinking it's an array.

@ViewChild queries elements defined in the component's own template. @ContentChild queries elements projected into the component from outside using ng-content. @ViewChild references are available in ngAfterViewInit while @ContentChild references are available earlier in ngAfterContentInit. Both support the static option for earlier access.
// ViewChild: queries own template
@ViewChild('myInput') input!: ElementRef;

// ContentChild: queries projected content
@ContentChild(HeaderComponent) header!: HeaderComponent;

// In parent template
<app-card>
  <app-header>Projected!</app-header>  <!-- queried by ContentChild -->
</app-card>
Think of ViewChild as querying your own template and ContentChild as querying what others put inside your component tags.

Why it matters: Tests understanding of template vs projected content. Shows you know component composition patterns.

Real applications: ViewChild accesses form inputs or child components you created. ContentChild accesses elements the parent projected in using ng-content.

Common mistakes: Developers mix up which decorator to use. They can't find projected content because they use @ViewChild. They think @ContentChild queries only direct children.

@Optional tells Angular that a dependency is not required and should not throw an error if it cannot be found. Without @Optional, Angular throws a NullInjectorError if the dependency is not provided anywhere in the injector tree. With @Optional, Angular simply injects null instead. This is useful for optional features or plugins.
constructor(
  @Optional() private analytics: AnalyticsService
) {}

trackEvent(name: string) {
  // Only track if analytics service is available
  this.analytics?.track(name);
}
Always check for null before using optional dependencies. Use the optional chaining operator (?.) for safe access.

Why it matters: Tests understanding of optional dependencies. Shows you can handle gracefully missing services.

Real applications: Optional analytics services, feature-specific services that may not be installed, plugin architectures, testing with optional mocks.

Common mistakes: Developers don't use @Optional causing injection errors. They forget null checks on optional dependencies. They use optional for everything instead of just truly optional features.

@Pipe decorator marks a class as an Angular pipe and gives it a name that can be used in template expressions. Every pipe class must implement the PipeTransform interface with a transform method. The name property defines the identifier used in templates with the pipe operator (|). Set pure to false for impure pipes that run on every change detection cycle.
@Pipe({
  name: 'truncate',
  standalone: true,
  pure: true // default
})
export class TruncatePipe implements PipeTransform {
  transform(value: string, limit = 50): string {
    return value.length > limit ? value.substring(0, limit) + '...' : value;
  }
}

// Usage: {{ longText | truncate:30 }}
Pipes can be standalone by setting standalone: true for use without NgModules. Pure pipes are more performant as they only run when input values change.

Why it matters: Tests understanding of pipe creation and optimization. Shows you can extend template functionality.

Real applications: Date pipes with custom formats, currency pipes with specific locales, text transformation pipes (uppercase, truncate), filter pipes.

Common mistakes: Developers create impure pipes unnecessarily harming performance. They forget to implement PipeTransform. They don't understand the pure flag changes when pipe runs.

@Attribute injects the value of an HTML attribute from the host element as a simple string. Unlike @Input, it only reads the attribute once at creation time and does not track changes. It is useful for static configuration values that never change. Since it does not create a binding, it is slightly more performant than @Input for pure string constants.
@Component({ selector: 'app-button' })
export class ButtonComponent {
  constructor(@Attribute('size') public size: string) {
    console.log(size); // 'large'
  }
}

// Usage: <app-button size="large"></app-button>
// Note: size is read once, NOT bound. [size]="var" won't update it.
The value is always a string or undefined. Use @Input instead if you need dynamic values that change over time.

Why it matters: Tests understanding of static vs dynamic attribute binding. Shows you know performance and change detection implications.

Real applications: Reading data attributes for configuration, reading custom attributes set by build tools, settings that never change during component lifetime.

Common mistakes: Developers use @Attribute for dynamic values then wonder why they don't update. They forget it's read at creation time only. They use @Attribute when plain @Input would work.

@Component is actually a subclass of @Directive with additional template-related metadata. @Directive creates directives that modify the behavior of existing elements without their own view. @Component creates directives that have their own template and view. Both support inputs, outputs, host bindings, lifecycle hooks, and dependency injection.
// Directive: adds behavior to existing elements
@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
  @HostListener('mouseenter') onHover() { /* highlight logic */ }
}

// Component: has its own template and view
@Component({
  selector: 'app-card',
  template: '<div class="card"><ng-content></ng-content></div>'
})
export class CardComponent { }
Use @Directive when you want to add behavior to existing elements. Use @Component when you need a reusable UI element with its own template.

Why it matters: Tests understanding of decorator hierarchy and when to use each. Shows you can build both reusable components and attribute behaviors.

Real applications: Directives: [appHighlight], [appDebounce], [appClickOutside]. Components: , , .

Common mistakes: Developers use @Component when @Directive would suffice losing reusability. They add templates to directives unnecessarily. They don't understand @Component extends @Directive functionality.