Angular

Components

17 Questions

A component is the fundamental building block of Angular applications. It controls a portion of the screen called a view and is made up of three main parts. The TypeScript class handles logic and data, the HTML template defines the UI, and optional CSS styles control appearance. Components are organized in a tree structure starting from the root component, with every Angular app having at least one root component that connects the component tree to the DOM. Components make your application modular, testable, and reusable across different parts of the app.
@Component({
  selector: 'app-hello',
  template: '<h1>Hello, {{name}}!</h1>',
  styles: ['h1 { color: blue; }']
})
export class HelloComponent {
  name = 'Angular';
}

// Usage: <app-hello></app-hello>
// Renders: <h1>Hello, Angular!</h1> in blue

Why it matters: This is a foundational question to assess your understanding of Angular's component-based architecture. It tests whether you grasp the three core pillars (logic, template, styles) and can explain how components form the foundation of Angular applications.

Real applications: Used in every Angular application. For example, an e-commerce site uses components like ProductCard (displays individual products), ShoppingCart (manages cart), Navbar (navigation), and Footer—each managing its own logic and view independently.

Common mistakes: Developers often create oversized "god components" that handle too much logic instead of breaking functionality into smaller, reusable components. They also forget that without the @Component decorator, Angular won't recognize the class as a component.

The @Component decorator marks a TypeScript class as an Angular component and provides configuration metadata that tells Angular how to create, render, and use the component in the application. The decorator accepts a configuration object with properties like selector, template/templateUrl, styles/styleUrls, and performance options like changeDetection and encapsulation. Without this decorator, Angular treats the class as a regular TypeScript class and will not recognize it as a component.
@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.Emulated
})
export class UserComponent {
  user = { name: 'John', age: 25 };
}

Why it matters: This tests whether you understand Angular's metadata system and decorators. It reveals if you know the important configuration properties and how they affect component behavior, performance, and styling isolation.

Real applications: Every Angular component uses the @Component decorator, such as a UserProfileComponent using templateUrl for external HTML, styleUrls for scoped CSS, and changeDetection: OnPush for optimization.

Common mistakes: Developers often forget to import Component from '@angular/core', confuse template vs templateUrl, or forget to use changeDetection: OnPush strategy which is crucial for optimization.

@Input allows a parent component to pass data down to a child component through property binding, establishing a clear one-directional data flow from parent to child. @Output lets the child emit events back to the parent using an EventEmitter, allowing the child to communicate changes back up the component tree. Together, they form the primary mechanism for direct parent-child communication in Angular. The parent uses square brackets [] for property binding to pass data and parentheses () for event binding to listen to emitted events. This pattern maintains clean, predictable data flow where data always goes down and events always bubble up.
// Child component
@Component({ 
  selector: 'app-child', 
  template: '<button (click)="send()">Send</button>' 
})
export class ChildComponent {
  @Input() message: string = '';
  @Output() notify = new EventEmitter<string>();
  
  send() { 
    this.notify.emit('Hello from child'); 
  }
}

// Parent template: <app-child [message]="parentMsg" (notify)="onNotify($event)"></app-child>

Why it matters: Tests understanding of Angular's data flow architecture and component interaction patterns. Interviewers verify you know how to properly structure parent-child relationships and handle two-way communication idiomatically.

Real applications: Email applications use this pattern with a parent MailboxComponent passing selected email data via @Input to a DetailComponent, which emits delete/archive events via @Output.

Common mistakes: Developers often misuse two-way binding instead of proper @Input/@Output, or try to communicate between sibling components through @Input/@Output when they should use a shared service.

View Encapsulation defines how component styles are scoped and isolated from the rest of the application, preventing CSS conflicts and style pollution. Angular supports three encapsulation modes to control this behavior. By default, Angular uses Emulated mode which adds unique attributes to elements to simulate shadow DOM behavior without using the actual Shadow DOM API, providing good isolation without browser compatibility concerns. ShadowDom uses native browser Shadow DOM for true isolation but has limited browser support and complexity. None removes all encapsulation making styles global, which should be avoided as it causes unpredictable style conflicts across components.
@Component({
  selector: 'app-demo',
  template: '<p>Styled text</p>',
  styles: ['p { color: red; }'],
  encapsulation: ViewEncapsulation.Emulated // default
})
export class DemoComponent { }

// The component element gets a unique attribute like: <app-demo _ngcontent-ng-c12345>
// Styles are scoped: p[_ngcontent-ng-c12345] { color: red; }

Why it matters: Tests knowledge of CSS scoping and component isolation strategies, critical for building maintainable applications without style conflicts. Interviewers assess your understanding of Angular's styling architecture and when to use different encapsulation modes.

Real applications: A header component with `.button { background: blue; }` using Emulated encapsulation won't affect button styling in a footer component with different button styles.

Common mistakes: Developers often rely on Emulated without understanding it's not true encapsulation, or use None thinking styles are global (they leak to child components with None mode).

Content projection allows you to insert external content into a component's template using the <ng-content> tag. It is Angular's version of slots, similar to React's children or Vue's slots. This makes components more flexible because the parent decides what content goes inside the child. You can use single-slot or multi-slot projection depending on your needs.
// Card component template
<div class="card">
  <div class="header"><ng-content select="[header]"></ng-content></div>
  <div class="body"><ng-content></ng-content></div>
</div>

// Usage
<app-card>
  <h2 header>Title</h2>
  <p>Body content here</p>
</app-card>
Multi-slot projection uses the select attribute to target elements by tag, class, or attribute. This pattern is essential for building reusable layout components like cards, modals, and tabs.

Dynamic components are created programmatically at runtime instead of being declared in a template. You use ViewContainerRef to create and insert them into the DOM dynamically. Since Angular 13+, you no longer need ComponentFactoryResolver — you can pass the component class directly. This approach is useful when you do not know which component to display until runtime.
@Component({
  selector: 'app-host',
  template: '<ng-container #container></ng-container>'
})
export class HostComponent {
  @ViewChild('container', { read: ViewContainerRef }) container!: ViewContainerRef;

  loadComponent() {
    this.container.clear();
    const ref = this.container.createComponent(DynamicComponent);
    ref.instance.data = 'Hello';
  }
}

Why it matters: Tests your understanding of Angular's advanced component creation mechanisms and when components need to be loaded dynamically based on application state.

Real applications: This pattern is commonly used for modals, tabs, or plugin systems where components are loaded on demand. You can pass data to dynamic components through the component reference instance properties.

Common mistakes: Developers often forget to call clear() before creating new dynamic components, leading to duplicate instances stacking up in the DOM.

Angular components have a clearly defined lifecycle managed by the framework. Each lifecycle hook is an interface method that Angular calls at specific moments. The most important hooks are ngOnInit for setup, ngOnChanges for reacting to input changes, and ngOnDestroy for cleanup. Understanding when each hook runs helps you write efficient and bug-free components.
export class MyComponent implements OnInit, OnChanges, OnDestroy {
  @Input() value!: string;

  ngOnChanges(changes: SimpleChanges) {
    // Called when @Input properties change
  }
  ngOnInit() {
    // Called once after first ngOnChanges; ideal for initialization
  }
  ngOnDestroy() {
    // Called before component is removed; clean up subscriptions
  }
}
The full order is: ngOnChangesngOnInitngDoCheckngAfterContentInitngAfterContentCheckedngAfterViewInitngAfterViewCheckedngOnDestroy. Hooks from ngDoCheck to ngAfterViewChecked repeat on every change detection cycle.

Why it matters: Tests understanding of component lifecycle and when to use different hooks for initialization, data updates, and cleanup.

Real applications: In a dashboard component, you fetch data in ngOnInit, respond to filter input changes in ngOnChanges, and unsubscribe from observables in ngOnDestroy.

Common mistakes: Developers often forget to unsubscribe from observables in ngOnDestroy, causing memory leaks, or try to access child components in ngOnInit instead of ngAfterViewInit.

Inline templates are defined directly in the @Component decorator using the template property as a string. External templates use a separate HTML file linked via templateUrl. Inline templates are good for small, simple views with a few lines of HTML. External templates keep the HTML separate from TypeScript for better organization and maintainability.
// Inline template
@Component({
  selector: 'app-inline',
  template: '<h1>{{title}}</h1><p>Inline template</p>'
})
export class InlineComponent { title = 'Hello'; }

// External template
@Component({
  selector: 'app-external',
  templateUrl: './external.component.html'
})
export class ExternalComponent { title = 'Hello'; }

Why it matters: Tests knowledge of Angular's flexibility in template definition and when each approach is appropriate for different scenarios.

Real applications: Inline templates work well for utility components like badges or spinners. External templates are essential for larger views like forms or dashboard pages, following Angular CLI conventions.

Common mistakes: Developers sometimes use inline templates for complex views making the TypeScript file huge and hard to read, or unnecessarily create external templates for trivial components.

Standalone components were introduced in Angular 14+ and do not need to be declared in an NgModule. They manage their own dependencies via the imports array in the decorator. This removes the need to create NgModules just to use a component. Standalone components are now the recommended default approach in Angular 17+ and simplify application architecture significantly.
@Component({
  selector: 'app-standalone',
  standalone: true,
  imports: [CommonModule, RouterModule],
  template: '<p *ngIf="show">I am standalone!</p>'
})
export class StandaloneComponent {
  show = true;
}

Why it matters: Tests understanding of modern Angular best practices and architectural simplification. Shows you're aware of Angular's evolution from NgModule-heavy to standalone-first approach.

Real applications: New projects and features use standalone components exclusively. Micro-frontends can be built as standalone components and composed together without NgModule overhead.

Common mistakes: Developers often forget to import required modules and directives in the imports array, then wonder why templates fail. They also mix standalone and NgModule components incorrectly.

Template reference variables provide a direct reference to a DOM element, component, or directive in the template. They are declared using the # symbol followed by a variable name. You can use them to access element properties, call component methods, or read directive values directly in the template. They are scoped to the template where they are defined.
<input #nameInput type="text" />
<button (click)="greet(nameInput.value)">Greet</button>

<!-- Reference to a child component -->
<app-timer #timer></app-timer>
<button (click)="timer.start()">Start Timer</button>
You can also access template references programmatically using @ViewChild:
@ViewChild('nameInput') input!: ElementRef;
ngAfterViewInit() { this.input.nativeElement.focus(); }
References are available after the ngAfterViewInit lifecycle hook. This gives you full control over DOM elements from your TypeScript code.

Why it matters: Tests understanding of template-component interaction and direct DOM access patterns in Angular.

Real applications: In a forms component, use #emailInput to focus on the email field after validation fails. In a video player component, use #videoElement to control playback.

Common mistakes: Developers often try to access template references in the constructor or ngOnInit when they're not yet available. They should only be accessed after ngAfterViewInit hook.

@ViewChild returns the first matching element from the component's template as a single reference. @ViewChildren returns all matching elements as a QueryList, which is useful when you have multiple instances of the same child. @ViewChild is available after ngAfterViewInit, and @ViewChildren also provides a changes observable so you can react when items are added or removed dynamically.
@ViewChild(ChildComponent) child!: ChildComponent;
@ViewChildren(ChildComponent) children!: QueryList<ChildComponent>;

ngAfterViewInit() {
  console.log(this.child);           // single instance
  console.log(this.children.length); // count of all instances
  this.children.changes.subscribe(list => console.log('changed', list.length));
}
Both decorators accept a component class, directive class, or template reference variable string as the selector. Use @ViewChild for single elements and @ViewChildren when dealing with dynamic lists.

Why it matters: Tests understanding of parent-child DOM query patterns and reactive list handling in Angular applications.

Real applications: In a todo list component, use @ViewChild to focus the input after adding an item. Use @ViewChildren to get all todo items and calculate statistics.

Common mistakes: Developers often forget that @ViewChild returns undefined if no match is found, leading to null reference errors. They also don't use the read parameter correctly when querying directives.

ng-container is a special Angular element that acts as a grouping element without rendering any extra DOM node. It is useful when you need to apply structural directives like *ngIf or *ngFor but do not want an additional wrapper element. This keeps your DOM clean and avoids unintended CSS side effects from extra elements. It is commonly used with ng-template for conditional content display.
<ng-container *ngIf="isLoggedIn">
  <p>Welcome back!</p>
  <button>Logout</button>
</ng-container>

<ng-container *ngFor="let item of items">
  <span>{{ item.name }}</span>
</ng-container>
Unlike a div or span, ng-container does not appear in the rendered DOM at all. This makes it perfect for applying directives without affecting your layout or styling.

Why it matters: Tests understanding of Angular's template syntax and ability to keep the DOM clean and semantic without unnecessary wrapper elements.

Real applications: Use ng-container in navigation bars to conditionally show admin controls only for admin users, or in lists to group items without adding extra list nesting.

Common mistakes: Developers often use div or span inside loops creating unnecessary wrapper elements in the DOM. They forget that ng-container is invisible in the rendered output, which is its main advantage.

ng-template defines a block of HTML that Angular does not render by default. It is only rendered when explicitly told to using structural directives like *ngIf else, *ngFor, or programmatically via ViewContainerRef. It acts as a blueprint for content that can be stamped out when needed. Angular replaces the ng-template tag itself — it never appears in the final DOM.
<div *ngIf="hasData; else loadingTpl">
  Data loaded!
</div>
<ng-template #loadingTpl>
  <p>Loading...</p>
</ng-template>
Use ng-template with the #ref syntax to define reusable template blocks. You can render them using ngTemplateOutlet or reference them in *ngIf else clauses.

Why it matters: Tests understanding of conditional rendering and template reusability patterns in Angular applications.

Real applications: Use ng-template with *ngIf to show loading states or error messages. Use ngTemplateOutlet to share template logic across multiple components.

Common mistakes: Developers often confuse ng-template with ng-container. They forget that templates are not rendered initially, so you must use a directive or ngTemplateOutlet to display them. They also don't realize templates can accept context variables.

The constructor is a standard TypeScript class feature that runs when the class is instantiated and is used for dependency injection only. ngOnInit is an Angular lifecycle hook that runs after Angular has set all @Input properties and initialized the component. You should use the constructor only to inject services and ngOnInit for initialization logic. This is because @Input values are not available in the constructor but are fully resolved by the time ngOnInit runs.
export class UserComponent implements OnInit {
  @Input() userId!: number;
  constructor(private userService: UserService) { }

  ngOnInit() {
    // userId is available here, NOT in constructor
    this.userService.getUser(this.userId);
  }
}
Use the constructor for DI injection and ngOnInit for data fetching, subscriptions, and property setup. This separation keeps your code clean and avoids timing issues with input bindings.

Why it matters: Tests understanding of Angular's initialization order and the correct place for different types of setup logic.

Real applications: In a UserProfileComponent, inject the UserService in the constructor, then fetch user data in ngOnInit after @Input userId is set.

Common mistakes: Developers often try to access @Input properties in the constructor when they're undefined. They also perform expensive operations (API calls, subscriptions) in the constructor instead of deferring to ngOnInit.

Smart components (also called container components) handle business logic, data fetching, and state management. They know about services and application logic. Dumb components (also called presentational components) only receive data through @Input and emit events through @Output. They do not know where the data comes from, making them highly reusable and easy to test.
// Smart (container) component
@Component({ template: '<app-user-list [users]="users" (select)="onSelect($event)"></app-user-list>' })
export class UserContainerComponent {
  users = this.userService.getUsers();
  constructor(private userService: UserService) {}
  onSelect(user: User) { this.router.navigate(['/user', user.id]); }
}

// Dumb (presentational) component
@Component({ selector: 'app-user-list', template: '...' })
export class UserListComponent {
  @Input() users: User[] = [];
  @Output() select = new EventEmitter<User>();
}
This separation follows the single responsibility principle and makes your app easier to maintain. Smart components orchestrate data flow while dumb components focus purely on presentation.

Why it matters: Tests understanding of component composition patterns and separation of concerns, fundamental to scalable Angular architectures.

Real applications: In an e-commerce app, UserContainerComponent fetches user data and passes it to UserDetailsComponent (dumb). UserDetailsComponent only knows about presentation and emits events.

Common mistakes: Developers often mix logic and presentation in a single component, making it hard to reuse or test. They forget that dumb components shouldn't know about services or routing.

Angular uses a change detection mechanism that checks each component from top to bottom in the component tree. By default, it checks every component on every browser event, timer, or HTTP response. You can optimize this by using ChangeDetectionStrategy.OnPush, which only checks when @Input references change, an event fires, or you trigger it manually. OnPush significantly improves performance in large applications.
@Component({
  selector: 'app-optimized',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: '<p>{{ data.name }}</p>'
})
export class OptimizedComponent {
  @Input() data!: { name: string };
}
With OnPush, always use immutable data patterns to ensure Angular detects changes properly. Use the async pipe or markForCheck() when data changes without a new @Input reference.

Why it matters: Tests understanding of Angular's change detection strategies and performance optimization fundamentals in large-scale applications.

Real applications: In a dashboard with 100+ data cards, using OnPush dramatically reduces the number of change detection cycles. Each card only updates when its @Input data reference changes.

Common mistakes: Developers often mutate data directly (e.g., array.push()) expecting OnPush to detect changes. They forget that OnPush requires new object/array references, not property mutations. They also forget to mark checked when using observables without the async pipe.

The host property in the @Component or @Directive decorator lets you bind properties, attributes, and event listeners directly on the host element. It provides a cleaner alternative to using @HostBinding and @HostListener decorators separately. The keys in the host object can be properties, attributes, or event names, and the values are expressions to evaluate. This is especially useful when you have multiple host bindings to configure.
@Component({
  selector: 'app-button',
  template: '<ng-content></ng-content>',
  host: {
    '[class.active]': 'isActive',
    '[attr.role]': '"button"',
    '(click)': 'onClick()',
    '[style.cursor]': '"pointer"'
  }
})
export class ButtonComponent {
  isActive = false;
  onClick() { this.isActive = !this.isActive; }
}
The host property keeps all host bindings in one place making the code easier to read. It is functionally equivalent to using separate @HostBinding and @HostListener decorators.

Why it matters: Tests understanding of component interaction with the host element and knowledge of alternative syntaxes for host bindings in Angular.

Real applications: In a custom button component, use the host property to add click handlers, set accessibility attributes, and apply dynamic classes all in one place.

Common mistakes: Developers often forget the syntax for property bindings [property] vs attribute bindings [attr.attrName] in the host object. They also don't realize that the host object and @HostBinding/@HostListener decorators are interchangeable.