@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.
@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.
// 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.
@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).
// 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.@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.
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: ngOnChanges → ngOnInit → ngDoCheck → ngAfterContentInit → ngAfterContentChecked → ngAfterViewInit → ngAfterViewChecked → ngOnDestroy. 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 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.
@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.
<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(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 *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.
<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.
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 (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.
@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.
@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.