Angular JS Interview questions: Coding Challenges
Master Angular with 100 challenges. Every card contains a clear problem statement, a detailed technical explanation, and the exact code solution.
Beginner Level (Core & Templates)
Q01
Implement one-way data binding.
BeginnerProblem: You need to display a dynamic string variable from the component class in the HTML template.
Details: Angular uses double curly braces
{{ }} for interpolation. This is one-way data binding from the component class to the template, ensuring the DOM updates when the class property changes.@Component({
template: '<h1>Hello, {{ name }}!</h1>',
})
export class UserComponent {
name = 'Alice';
}
Q02
Implement property binding.
BeginnerProblem: You need to dynamically disable a button based on a boolean state variable.
Details: Use square brackets
[property] to bind a DOM element’s property to a component class variable. Unlike interpolation, property binding safely sets boolean states on DOM nodes (like disabled).@Component({
template: '<button [disabled]="isProcessing">Submit</button>'
})
export class SubmitComponent {
isProcessing = true;
}
Q03
Implement event binding.
BeginnerProblem: You need to execute a component method when a user clicks a button.
Details: Use parentheses
(event) to bind a DOM event to a method in your class. This handles user interactions like clicks, keystrokes, and form submissions.@Component({
template: '<button (click)="onClick()">Click Me</button>'
})
export class ClickComponent {
onClick() { console.log('Clicked!'); }
}
Q04
Implement two-way data binding.
BeginnerProblem: You need an input field to update a variable in real-time, and changing the variable should update the input field.
Details: Use the “banana-in-a-box” syntax
[(ngModel)]. This combines property binding and event binding. You must import FormsModule to use it.// Requires: import { FormsModule } from '@angular/forms';
@Component({
imports: [FormsModule],
template: '<input [(ngModel)]="user" /> <p>{{ user }}</p>'
})
export class InputComponent {
user = '';
}
Q05
Use modern control flow `@if`.
BeginnerProblem: Conditionally render an HTML element based on a boolean value.
Details: Angular v17 introduced the built-in
@if block, replacing *ngIf. It provides better performance, cleaner syntax, and doesn’t require importing CommonModule.@Component({
template: `
@if (isVisible) { <div>Visible</div> }
@else { <div>Hidden</div> }
`
})
export class ToggleComponent {
isVisible = true;
}
Q06
Use modern control flow `@for`.
BeginnerProblem: Loop over an array of items and render an
<li> for each.Details: Angular v17 introduced
@for, replacing *ngFor. It mandates a track expression for performance, completely eliminating the need for a separate trackBy function.@Component({
template: `
<ul>
@for (item of items; track item.id) {
<li>{{ item.name }}</li>
}
</ul>
`
})
export class ListComponent {
items = [{ id: 1, name: 'Apple' }];
}
Q07
Use the `@empty` block.
BeginnerProblem: Show a fallback “No items” message when an array is empty during a loop.
Details: The new
@for block seamlessly integrates an @empty block that renders automatically if the provided array has a length of zero.@Component({
template: `
@for (item of items; track item.id) {
<div>{{ item.name }}</div>
} @empty {
<div>No items found.</div>
}
`
})
Q08
Pass data to a Child Component.
BeginnerProblem: A parent component needs to pass a configuration string into a child component.
Details: Decorate a property in the child component with
@Input(). The parent can then use property binding [propName] to pass data in.// Child
export class ChildComp { @Input() config!: string; }
// Parent Template
<app-child [config]="'Dark Mode'"></app-child>
Q09
Emit an event to a Parent Component.
BeginnerProblem: A child component needs to notify its parent when an action occurs.
Details: Use
@Output() paired with an EventEmitter. The child calls .emit(data), and the parent listens using standard event binding (eventName).// Child
export class ChildComp {
@Output() action = new EventEmitter<string>();
trigger() { this.action.emit('Done!'); }
}
// Parent Template
<app-child (action)="handleAction($event)"></app-child>
Q10
Apply a CSS class dynamically.
BeginnerProblem: Toggle an ‘active’ CSS class on a div based on a component property.
Details: Use class binding
[class.className]="condition". It adds the class if the condition is truthy and removes it if falsy.@Component({
template: '<div [class.active]="isActive">Status</div>'
})
export class StyleComp { isActive = true; }
Q11
Apply inline styles dynamically.
BeginnerProblem: Change text color to red or green depending on an error state.
Details: Use style binding
[style.property]="value" for single styles, or [ngStyle] for multiple dynamically evaluated styles.@Component({
template: '<div [style.color]="isErr ? \'red\' : \'green\'">Text</div>'
})
export class StyleComp { isErr = true; }
Q12
Format a date using DatePipe.
BeginnerProblem: A raw JavaScript Date object needs to be displayed in a human-readable format.
Details: Use the
| date pipe in the template. You can pass arguments like 'shortDate' to customize the output format without mutating the actual Date object.@Component({
imports: [DatePipe],
template: '<p>{{ today | date:"shortDate" }}</p>'
})
export class DateComp { today = new Date(); }
Q13
Format a number as currency.
BeginnerProblem: Display a float value as standard US currency with a dollar sign.
Details: Use the
| currency pipe. It automatically adds the correct symbol, commas, and restricts decimals based on the provided currency code.@Component({
imports: [CurrencyPipe],
template: '<p>Total: {{ price | currency:"USD" }}</p>'
})
export class PriceComp { price = 199.99; }
Q14
Create a Custom Pipe.
BeginnerProblem: You need a reusable way to transform strings to fully uppercase across your app.
Details: Create a class decorated with
@Pipe and implement the PipeTransform interface. The transform method takes the input and returns the transformed string.@Pipe({ name: 'customUpper', standalone: true })
export class UpperPipe implements PipeTransform {
transform(val: string): string {
return val ? val.toUpperCase() : '';
}
}
Q15
Create a Standalone Component.
BeginnerProblem: Create a modern component that doesn’t rely on being declared in an NgModule.
Details: Set
standalone: true in the @Component decorator. This allows the component to directly import its own dependencies and be bootstrapped independently.@Component({
selector: 'app-solo',
standalone: true,
template: '<h1>Standalone!</h1>'
})
export class SoloComponent {}
Q16
Execute code on Initialization.
BeginnerProblem: Fetch data from an API exactly once when the component first appears.
Details: Implement the
OnInit interface and place your logic inside ngOnInit(). This fires once after Angular has initialized data-bound input properties.export class InitComp implements OnInit {
ngOnInit() {
console.log('Fired once on init!');
}
}
Q17
Execute code on Destruction.
BeginnerProblem: Prevent memory leaks by clearing a
setInterval when a component is removed from the DOM.Details: Implement
OnDestroy. The ngOnDestroy() method runs right before the component is destroyed, making it perfect for cleanup tasks.export class DestroyComp implements OnDestroy {
ngOnDestroy() {
console.log('Clean up subscriptions here!');
}
}
Q18
React to `@Input` changes.
BeginnerProblem: Execute specific logic every time a parent passes a new value to an
@Input property.Details: Implement
OnChanges. The ngOnChanges() method provides a SimpleChanges object containing the previous and current values of all bound inputs.export class ChangeComp implements OnChanges {
@Input() data!: string;
ngOnChanges(changes: SimpleChanges) {
if (changes['data']) console.log('Changed!');
}
}
Q19
Implement Content Projection (Slots).
BeginnerProblem: Create a reusable Card component that allows parents to pass arbitrary HTML inside it.
Details: Place the
<ng-content></ng-content> tag inside the child component’s template. Anything the parent puts between the child’s opening and closing tags will be injected there.// Child Template
`<div class="card"> <ng-content></ng-content> </div>`
// Parent Usage
<app-card> <h1>Projected!</h1> </app-card>
Q20
Use Template Reference Variables.
BeginnerProblem: Read the value of an input field directly in the HTML template without tying it to a component class variable.
Details: Assign a hash
#varName to an element. You can then reference that DOM node and its properties anywhere else within the same template.@Component({
template: `
<input #myInput />
<button (click)="log(myInput.value)">Log</button>
`
})
export class RefComp {
log(v: string) { console.log(v); }
}
Q21
Create an Injectable Service.
BeginnerProblem: Create a reusable class to hold business logic and data that can be shared across multiple components.
Details: Use the
@Injectable decorator. Setting providedIn: 'root' registers it as a singleton across the entire application automatically.@Injectable({ providedIn: 'root' })
export class DataService {
getItems() { return ['A', 'B']; }
}
Q22
Inject a Service using `inject()`.
BeginnerProblem: Access a service inside a component without using constructor injection.
Details: Angular v14 introduced the
inject() function. It is cleaner than constructor injection and heavily utilized in modern function-based Angular patterns.import { inject } from '@angular/core';
export class MyComp {
private dataService = inject(DataService);
items = this.dataService.getItems();
}
Q23
Define a basic route array.
BeginnerProblem: Map a URL path to load a specific component.
Details: Create an array of
Route objects defining the path and the component that should render when the browser hits that URL.export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent }
];
Q24
Navigate using RouterLink.
BeginnerProblem: Create an anchor tag that navigates to a new route without causing a full browser page reload.
Details: Use the
routerLink directive instead of href. It intercepts the click and updates the URL via Angular’s internal router.// Requires importing RouterModule or RouterLink
@Component({
imports: [RouterLink],
template: '<a routerLink="/about">Go to About</a>'
})
Q25
Navigate programmatically.
BeginnerProblem: Redirect the user to a new route from inside a component method (e.g., after saving a form).
Details: Inject the
Router service and call its navigate() method, passing an array representing the path segments.export class SaveComp {
private router = inject(Router);
onSave() {
// Save logic here...
this.router.navigate(['/dashboard']);
}
}
Intermediate Level (Forms, Routing & RxJS Basics)
Q26
Extract a static route parameter.
IntermediateProblem: Read the
id parameter from the URL /users/:id exactly once when the component loads.Details: Inject
ActivatedRoute and access the snapshot.paramMap. This is synchronous and ideal if the component never reuses the same instance for different URLs.export class UserComp implements OnInit {
private route = inject(ActivatedRoute);
userId!: string;
ngOnInit() {
this.userId = this.route.snapshot.paramMap.get('id')!;
}
}
Q27
Extract route parameters reactively.
IntermediateProblem: Read URL parameters in a way that handles the URL changing while the component remains mounted.
Details: Subscribe to
route.paramMap. Because it is an Observable, the callback fires every time the route parameter updates without unmounting the component.export class UserComp implements OnInit {
private route = inject(ActivatedRoute);
ngOnInit() {
this.route.paramMap.subscribe(params => {
console.log('New ID:', params.get('id'));
});
}
}
Q28
Setup a Template-Driven form.
IntermediateProblem: Create a form primarily governed by HTML attributes, capturing its value on submit.
Details: Import
FormsModule. Use #form="ngForm" on the form tag and add the ngModel directive to inputs. Pass form.value to the submit handler.@Component({
template: `
<form #f="ngForm" (ngSubmit)="submit(f.value)">
<input name="email" ngModel required />
<button [disabled]="f.invalid">Save</button>
</form>
`
})
export class FormComp {
submit(val: any) { console.log(val); }
}
Q29
Setup a Reactive Form.
IntermediateProblem: Define a complex form structure and validation rules directly in the TypeScript class.
Details: Inject
FormBuilder to cleanly construct a FormGroup. Reactive forms offer better testability and synchronous access to form state.export class ReactiveComp {
private fb = inject(FormBuilder);
myForm = this.fb.group({
email: ['', [Validators.required, Validators.email]],
age: [18, Validators.min(18)]
});
}
Q30
Bind a Reactive Form to the template.
IntermediateProblem: Connect the
FormGroup instantiated in your class to the HTML form elements.Details: Import
ReactiveFormsModule. Apply [formGroup] to the `<!-- Ensure ReactiveFormsModule is imported -->
<form [formGroup]="myForm">
<input formControlName="email" />
<input formControlName="age" type="number" />
</form>
Q31
Dynamically add controls with FormArray.
IntermediateProblem: Create a form where users can add an unknown number of input fields (e.g., adding multiple aliases).
Details: Use
FormBuilder.array(). You can programmatically push new FormControl or FormGroup objects into this array at runtime based on user action.export class FormComp {
private fb = inject(FormBuilder);
aliases = this.fb.array([ this.fb.control('') ]);
addAlias() {
this.aliases.push(this.fb.control(''));
}
}
Q32
Make a GET request using HttpClient.
IntermediateProblem: Fetch data from a REST API endpoint and return it as an Observable.
Details: Inject
HttpClient and call its get() method. Providing a generic type <Type> ensures the resulting Observable is strongly typed.@Injectable({ providedIn: 'root' })
export class ApiService {
private http = inject(HttpClient);
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users');
}
}
Q33
Make a POST request using HttpClient.
IntermediateProblem: Send JSON payload data to a server endpoint to create a new resource.
Details: Use
http.post(). The first argument is the URL, and the second is the body payload. Angular automatically serializes the object to JSON.export class ApiService {
private http = inject(HttpClient);
createUser(user: User) {
return this.http.post('/api/users', user);
}
}
Q34
Use the
Intermediateasync pipe.Problem: Render data from an Observable directly in the HTML without manually subscribing in the TypeScript class.
Details: The
| async pipe subscribes to an Observable in the template, unwraps the emitted values, and automatically unsubscribes when the component is destroyed.@Component({
imports: [AsyncPipe],
template: `
@for (u of users$ | async; track u.id) {
<div>{{ u.name }}</div>
}
`
})
export class UserComp {
users$ = inject(ApiService).getUsers();
}
Q35
Catch errors in an RxJS stream.
IntermediateProblem: Intercept a failed HTTP request to log the error and return a safe fallback value to the UI.
Details: Use the
catchError operator within the pipe(). It must return a new Observable (like of([])) so the downstream subscriptions don’t crash.this.http.get('/api/data').pipe(
catchError(err => {
console.error('Failed:', err);
return of([]); // Fallback empty array
})
);
Q36
Transform data with the
Intermediatemap operator.Problem: Filter or restructure the data coming from an HTTP response before it reaches the component.
Details: Use the
map operator inside pipe(). It takes the emitted data, allows you to mutate/transform it, and passes the result down the observable chain.this.http.get<User[]>('/api/users').pipe(
// Map RxJS operator, calling native Array.filter
map(users => users.filter(u => u.isActive))
);
Q37
Create state using BehaviorSubject.
IntermediateProblem: Hold a piece of state in a service that requires an initial value and emits its current value to any new subscribers instantly.
Details: A
BehaviorSubject holds the “current” value. Expose it as an Observable using asObservable() to prevent external classes from calling next().export class StateService {
private userSub = new BehaviorSubject<string>('Guest');
user$ = this.userSub.asObservable();
setUser(name: string) { this.userSub.next(name); }
}
Q38
Manually unsubscribe from an Observable.
IntermediateProblem: Prevent memory leaks by destroying an active manual subscription when a component leaves the DOM.
Details: Store the returned
Subscription object in a class property, and call unsubscribe() on it inside the ngOnDestroy lifecycle hook.export class MyComp implements OnDestroy {
private sub!: Subscription;
ngOnInit() {
this.sub = this.service.data$.subscribe();
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
Q39
Cleanly unsubscribe using
IntermediatetakeUntilDestroyed.Problem: Unsubscribe from an Observable without writing boilerplate
ngOnDestroy logic.Details: Angular v16 introduced
takeUntilDestroyed. Placed inside a pipe (and called in an injection context like the constructor), it automatically kills the stream on destroy.import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export class ModernComp {
constructor() {
this.service.data$.pipe(
takeUntilDestroyed()
).subscribe();
}
}
Q40
Query a child component using
Intermediate@ViewChild.Problem: A parent component needs to call a method or access properties directly on a child component.
Details:
@ViewChild allows you to grab a reference to an element or component injected in the template. The reference is guaranteed to be available by ngAfterViewInit.export class ParentComp implements AfterViewInit {
@ViewChild(ChildComp) child!: ChildComp;
ngAfterViewInit() {
this.child.childMethod();
}
}
Q41
Query projected content using
Intermediate@ContentChild.Problem: A wrapper component needs to inspect or access a component passed into it via
<ng-content>.Details: While ViewChild looks at the component’s own template,
@ContentChild looks at the nodes projected into it from the parent. Available by ngAfterContentInit.export class WrapperComp implements AfterContentInit {
@ContentChild(HeaderComp) header!: HeaderComp;
ngAfterContentInit() {
console.log(this.header);
}
}
Q42
Lazy load a standalone route.
IntermediateProblem: Defer downloading a component’s JavaScript bundle until the user actually navigates to that route.
Details: Use the
loadComponent property in your route definition paired with a dynamic import() statement pointing to the standalone component file.export const routes: Routes = [
{
path: 'admin',
loadComponent: () => import('./admin.comp').then(c => c.AdminComp)
}
];
Q43
Bind to the Host element’s properties.
IntermediateProblem: A directive or component needs to apply a CSS class or attribute directly to its own hosting DOM element.
Details: Use the
@HostBinding decorator. It binds a host element property (like a class or style) to a variable inside the directive class.@Directive({ selector: '[appHighlight]', standalone: true })
export class HighlightDirective {
@HostBinding('class.highlighted') isHigh = true;
}
Q44
Listen to Host element events.
IntermediateProblem: A directive needs to trigger a function when the user clicks or hovers over the element it is attached to.
Details: Use the
@HostListener decorator. It automatically listens for standard DOM events on the host element and triggers the bound class method.@Directive({ selector: '[appClickTrack]', standalone: true })
export class ClickTrackDirective {
@HostListener('click', ['$event'])
onClick(e: Event) { console.log('Clicked!', e); }
}
Q45
Combine multiple Observables.
IntermediateProblem: You need to merge data from a User stream and a Posts stream before rendering the UI.
Details: Use the RxJS
combineLatest function. It waits for all provided observables to emit at least once, then emits an array of their latest values whenever any of them update.vm$ = combineLatest([this.users$, this.posts$]).pipe(
map(([users, posts]) => ({ users, posts }))
);
Q46
Avoid duplicate HTTP calls using
IntermediateshareReplay.Problem: An HTTP observable is subscribed to by multiple components, causing the network request to fire redundantly for each subscriber.
Details: Add
shareReplay(1) to the end of the pipe. It multicasts the observable and caches the last emitted value, serving it instantly to late subscribers.export class DataService {
config$ = this.http.get('/api/config').pipe(
shareReplay(1)
);
}
Q47
Setup a wildcard (404) route.
IntermediateProblem: Redirect the user to a “Not Found” component if they type an invalid URL.
Details: Add a route at the very bottom of your routing array with the path
'**'. The router checks top-down, so this acts as a catch-all.export const routes: Routes = [
{ path: '', component: HomeComp },
{ path: '**', component: NotFoundComp }
];
Q48
Setup a functional Route Guard.
IntermediateProblem: Prevent unauthorized users from accessing a specific route in your application.
Details: Modern Angular replaces Guard Classes with
CanActivateFn functions. Use inject() to access auth services and return a boolean or a redirect UrlTree.export const authGuard: CanActivateFn = () => {
const isAuth = inject(AuthService).isLoggedIn;
return isAuth ? true : inject(Router).parseUrl('/login');
};
// Route config: { path: 'dash', canActivate: [authGuard] }
Q49
Attach HTTP Headers to a request.
IntermediateProblem: Send custom metadata, like a specific Content-Type or Auth token, with an individual HttpClient request.
Details: Pass an options object as the final parameter to the
http.get/post method containing an instance of HttpHeaders.const headers = new HttpHeaders({ 'X-Custom-Header': 'Value' });
this.http.get('/api/data', { headers }).subscribe();
Q50
Create a Custom Form Validator.
IntermediateProblem: Enforce a rule that a specific input cannot contain the word "admin".
Details: A custom validator is simply a function that takes an
AbstractControl. It returns null if valid, or a ValidationErrors object if invalid.export function noAdminValidator(): ValidatorFn {
return (ctrl: AbstractControl): ValidationErrors | null => {
const isForbidden = ctrl.value?.includes('admin');
return isForbidden ? { noAdmin: true } : null;
};
}
Advanced Level (Architecture, Signals, & RxJS Patterns)
Q51
Create an HTTP Interceptor.
AdvancedProblem: Automatically attach a JWT Bearer token to every outgoing HTTP request globally.
Details: Create an
HttpInterceptorFn. It intercepts the HttpRequest, allows you to clone and mutate headers, and forwards it via the next() handler.export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = 'my-jwt-token';
const cloned = req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
return next(cloned);
};
Q52
Cancel previous API requests using
AdvancedswitchMap.Problem: Prevent race conditions in an autocomplete search where typing fast triggers multiple overlapping requests.
Details:
switchMap maps values to an inner observable. Crucially, if a new value arrives before the inner observable finishes, it cancels/aborts the previous network request.this.searchControl.valueChanges.pipe(
switchMap(term => this.http.get(`/api/search?q=${term}`))
).subscribe(results => console.log(results));
Q53
Queue operations safely using
AdvancedconcatMap.Problem: Process multiple HTTP POST requests in strict order, ensuring one finishes before the next begins.
Details: Unlike switchMap (cancels) or mergeMap (runs in parallel),
concatMap waits for the previous inner observable to complete before executing the next one.this.saveSubject.pipe(
concatMap(data => this.http.post('/api/save', data))
).subscribe();
Q54
Debounce an input stream.
AdvancedProblem: Wait until a user has paused typing for 300ms before sending a value down the observable chain.
Details: Use
debounceTime(ms). It discards emitted values that take less than the specified time between outputs. Pair with distinctUntilChanged() to ignore identical consecutive values.this.inputControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged()
).subscribe(val => console.log(val));
Q55
Implement an Async Form Validator.
AdvancedProblem: Validate an input field by querying a database to see if a username is already taken.
Details: An
AsyncValidatorFn returns an Observable instead of a static value. Angular waits for the observable to complete before updating the form's validity state.export function emailTakenValidator(api: ApiService): AsyncValidatorFn {
return (ctrl) => api.checkEmail(ctrl.value).pipe(
map(isTaken => isTaken ? { emailTaken: true } : null)
);
}
Q56
Boost performance with
AdvancedOnPush Change Detection.Problem: A heavy component re-renders unnecessarily whenever a global application state changes.
Details: Setting
ChangeDetectionStrategy.OnPush tells Angular to skip checking this component unless its @Input() object references change, or an event originates from it.@Component({
selector: 'app-fast',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<p>{{ data.val }}</p>'
})
export class FastComp { @Input() data: any; }
Q57
Manually trigger Change Detection.
AdvancedProblem: An
OnPush component updates a variable via a setTimeout or external observable, but the UI doesn't refresh.Details: Inject
ChangeDetectorRef and call markForCheck(). This flags the component's branch so Angular knows to evaluate it during the next tick.export class ManualComp {
private cdr = inject(ChangeDetectorRef);
updateLocalData() {
this.data.val = 'New';
this.cdr.markForCheck();
}
}
Q58
Bind route parameters directly to component Inputs.
AdvancedProblem: Avoid writing boilerplate
ActivatedRoute subscription logic just to read a simple URL parameter.Details: Use
withComponentInputBinding() in your router config. Angular will automatically push URL params (like /user/:id) directly into matching @Input() properties.// In app.config.ts router setup:
provideRouter(routes, withComponentInputBinding())
// In UserComponent (URL: /user/42):
@Input() id!: string; // Automatically gets '42'
Q59
Handle navigation cancellation gracefully.
AdvancedProblem: Warn a user if they attempt to navigate away from a form with unsaved changes.
Details: Create a
CanDeactivateFn guard. If the function returns false (or a confirmed prompt returning false), the Angular router aborts the navigation attempt entirely.export const unsavedGuard: CanDeactivateFn<any> = (comp) => {
if (comp.hasUnsavedChanges) {
return confirm('Unsaved changes. Leave?');
}
return true;
};
Q60
Create and update a basic Signal.
AdvancedProblem: Use Angular's modern reactivity model to hold primitive state without relying on RxJS or Zone.js hooks.
Details: Use the
signal() function. Read it by calling it like a function count(), and mutate it using .set(val) or .update(fn).import { signal } from '@angular/core';
export class CounterComp {
count = signal(0);
increment() {
this.count.update(c => c + 1);
}
}
Q61
Compute derived state using Signals.
AdvancedProblem: Create a reactive variable that automatically recalculates whenever its dependent signals change.
Details: Use the
computed() function. It caches its calculation and only re-evaluates lazily when the internal signals it reads notify it of a change.export class CartComp {
price = signal(100);
tax = computed(() => this.price() * 0.2);
total = computed(() => this.price() + this.tax());
}
Q62
Run side effects reacting to a Signal.
AdvancedProblem: Log a message or execute imperative code whenever a specific signal's value changes.
Details: Use
effect(). It registers a reactive side-effect that automatically tracks any signals read within its closure, re-running asynchronously when they update.export class LogComp {
id = signal(1);
constructor() {
effect(() => {
console.log('ID is now:', this.id());
});
}
}
Q63
Define a Signal Input.
AdvancedProblem: Replace the traditional
@Input() decorator with the modern, safer Signal-based input API.Details: Use the
input() or input.required() function. It exposes the passed data as a read-only Signal, ensuring perfect type safety and reactive integration.import { input } from '@angular/core';
export class UserCardComp {
userId = input.required<number>();
theme = input('light'); // Optional with default
}
Q64
Use
Advanced@defer for lazy loading components in the template.Problem: You want to split a heavy charting library into a separate JS chunk that only loads when scrolled into view.
Details: Use the
@defer block with a trigger like on viewport. It handles chunking automatically without router configurations, providing a @placeholder while waiting.@Component({
template: `
@defer (on viewport) {
<heavy-chart />
} @placeholder {
<div>Scroll down to load chart...</div>
}
`
})
export class DashComp {}
Q65
Implement ControlValueAccessor.
AdvancedProblem: Build a custom UI component (like a star rating) that plugs seamlessly into Angular's
formControlName or ngModel directives.Details: Implement
ControlValueAccessor and provide it via NG_VALUE_ACCESSOR. This interface acts as the bridge translating Angular form APIs to your component's internal state.@Component({
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: CustomInput, multi: true }]
})
export class CustomInput implements ControlValueAccessor {
value = '';
onChange = (val: any) => {};
writeValue(val: any) { this.value = val; }
registerOnChange(fn: any) { this.onChange = fn; }
registerOnTouched(fn: any) {}
}
Q66
Provide environment variables via InjectionToken.
AdvancedProblem: Inject a static configuration string (like an API URL) into a service safely without hardcoding it.
Details: Instantiate an
InjectionToken. Provide a value for it in your app's config array, and consume it using the standard inject() function.export const API_URL = new InjectionToken<string>('API_URL');
// In app.config.ts
{ provide: API_URL, useValue: 'https://api.com' }
// In Service
private url = inject(API_URL);
Q67
Create an Observable manually from a DOM event.
AdvancedProblem: Turn a raw browser event (like document clicks) into a subscribable RxJS stream without using
fromEvent.Details: Instantiate a
new Observable. Bind the native listener inside, emit via subscriber.next(), and return a teardown function that removes the listener.const clicks$ = new Observable(sub => {
const handler = (e: Event) => sub.next(e);
document.addEventListener('click', handler);
return () => document.removeEventListener('click', handler);
});
Q68
Handle errors globally.
AdvancedProblem: Catch all unhandled UI exceptions across the entire app to log them to a telemetry service like Sentry.
Details: Create a class implementing
ErrorHandler and override its handleError method. Provide this class in your application root providers.@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
handleError(error: any) {
console.error('Global Catch:', error);
// Post to Sentry/DataDog here
}
}
// Provider: { provide: ErrorHandler, useClass: GlobalErrorHandler }
Q69
Dynamically create a component.
AdvancedProblem: Render a component into the DOM purely via TypeScript logic, without declaring it in an HTML template.
Details: Inject
ViewContainerRef. Call createComponent() passing the Component class. You can assign inputs directly to the returned reference's instance.export class HostComp {
private vcr = inject(ViewContainerRef);
loadComp() {
this.vcr.clear();
const ref = this.vcr.createComponent(DynamicComp);
ref.instance.data = 'Passed programmatically';
}
}
Q70
Convert an Observable to a Signal.
AdvancedProblem: Bridge an existing RxJS data stream (like an HTTP fetch) into Angular's modern Signal ecosystem.
Details: Use
toSignal() from @angular/core/rxjs-interop. It subscribes to the observable instantly and returns a read-only Signal representing the latest value.import { toSignal } from '@angular/core/rxjs-interop';
export class DataComp {
private data$ = this.http.get('/api/data');
dataSig = toSignal(this.data$);
}
Q71
Convert a Signal to an Observable.
AdvancedProblem: You have a Signal but need to pass its value into a legacy RxJS pipe chain (like
switchMap).Details: Use
toObservable(). It tracks the signal using an effect() internally and pushes new values to subscribers whenever the signal updates.import { toObservable } from '@angular/core/rxjs-interop';
export class RxjsBridgeComp {
count = signal(0);
count$ = toObservable(this.count);
constructor() {
this.count$.subscribe(c => console.log(c));
}
}
Q72
Cache HTTP requests via Interceptors.
AdvancedProblem: Prevent duplicate network calls for identical URLs by intercepting requests and returning cached responses.
Details: Maintain a Map dictionary. Check if the URL exists in the map; if yes, return
of(cachedResponse). If no, pass the request on and use tap to save the final response into the map.const cache = new Map<string, HttpResponse<any>>();
export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
if (req.method !== 'GET') return next(req);
if (cache.has(req.urlWithParams)) return of(cache.get(req.urlWithParams)!);
return next(req).pipe(
tap(res => { if (res instanceof HttpResponse) cache.set(req.urlWithParams, res); })
);
};
Q73
Implement a highly-optimized trackBy function.
AdvancedProblem: Write a generic tracking function to prevent DOM recreation in legacy
*ngFor loops.Details: Create a factory function returning a
TrackByFunction. This keeps code DRY if you map many arrays by generic keys like 'id'. (Note: mostly obsolete with `@for`).export function trackByProp<T>(prop: keyof T): TrackByFunction<T> {
return (_, item) => item[prop];
}
// Component: trackById = trackByProp('id');
// Template: *ngFor="let i of items; trackBy: trackById"
Q74
Execute code strictly on App Startup.
AdvancedProblem: Halt the application from rendering until a critical configuration file is fetched via API.
Details: Use
APP_INITIALIZER. Provide a factory function that returns a Promise or Observable. Angular blocks bootstrapping until it resolves.export function initApp(config: ConfigService) {
return () => config.load(); // Returns Promise
}
// Providers:
{ provide: APP_INITIALIZER, useFactory: initApp, deps: [ConfigService], multi: true }
Q75
Create a two-way bound Model Signal.
AdvancedProblem: Implement standard two-way data binding (like `[(ngModel)]`) using the modern Signal API.
Details: Use the
model() function. It acts as both an Input (receives data from parent) and an Output (emits updates back to parent implicitly).import { model } from '@angular/core';
export class CheckboxComp {
// Parent uses: [(checked)]="val"
checked = model(false);
toggle() {
this.checked.update(v => !v);
}
}
Expert Level (Performance, SSR, Architecture)
Q76
Prevent zone.js from tracking DOM events.
ExpertProblem: A rapid firing event (like
scroll or mousemove) triggers Change Detection thousands of times, freezing the UI.Details: Inject
NgZone and wrap the event listener inside runOutsideAngular. The callback will execute invisibly to the change detection engine.export class ScrollComp implements OnInit {
private ngZone = inject(NgZone);
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
window.addEventListener('scroll', () => {
// Heavy logic here
});
});
}
}
Q77
Re-enter Angular Zone from an external callback.
ExpertProblem: After finishing heavy work outside the Angular zone, you need to update a component state and reflect it in the DOM.
Details: Inside your outside-zone code, wrap the state update logic in
ngZone.run(). This forcefully brings execution back into Angular's purview, triggering a render tick.this.ngZone.runOutsideAngular(() => {
heavyApi((result) => {
this.ngZone.run(() => {
this.data = result; // UI updates now
});
});
});
Q78
Isolate code from executing during SSR.
ExpertProblem: Your component uses
window.localStorage, which crashes the Node.js server during Server-Side Rendering.Details: Inject
PLATFORM_ID and evaluate it using isPlatformBrowser(). Wrap the problematic DOM-specific APIs in this conditional.import { isPlatformBrowser } from '@angular/common';
export class SsrComp {
private platformId = inject(PLATFORM_ID);
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
window.localStorage.setItem('key', 'val');
}
}
}
Q79
Transfer state from Server to Client (SSR).
ExpertProblem: Prevent the browser from re-fetching the exact same API data that the Node server already fetched during the SSR process.
Details: Inject
TransferState. The server caches the API response in an inline script block. The client intercepts the fetch, checks the TransferState key, and uses the cached data instead.export class DataSvc {
private transferState = inject(TransferState);
private KEY = makeStateKey<any>('MY_DATA');
getData() {
if (this.transferState.hasKey(this.KEY)) {
return of(this.transferState.get(this.KEY, null));
}
return this.http.get('/api').pipe(
tap(data => this.transferState.set(this.KEY, data))
);
}
}
Q80
Build a Custom Method Decorator.
ExpertProblem: You want a clean, reusable way to log the exact execution time of a method simply by adding
@LogTime() above it.Details: Create a factory function returning a PropertyDescriptor manipulator. It overrides the original method with a wrapper that runs
console.time around the execution block.export function LogTime() {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.time(key);
const result = original.apply(this, args);
console.timeEnd(key);
return result;
};
};
}
Q81
Preload all lazy modules automatically.
ExpertProblem: Maximize performance by downloading lazy-loaded JS bundles in the background immediately after the initial page renders.
Details: Wrap
PreloadAllModules inside the withPreloading() function in the router configuration.// In app.config.ts
provideRouter(routes, withPreloading(PreloadAllModules))
Q82
Write a Custom Preloading Strategy.
ExpertProblem: Only preload specific lazy modules (e.g., highly trafficked features) while keeping heavy, rarely-used modules purely lazy.
Details: Implement the
PreloadingStrategy interface. Check custom route.data tags (like preload: true) to decide whether to invoke the load() callback.@Injectable({ providedIn: 'root' })
export class FlaggedPreloadStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
return route.data?.['preload'] ? load() : of(null);
}
}
Q83
Compile Angular Components to Web Components.
ExpertProblem: Render an Angular component natively inside a React, Vue, or vanilla HTML application using browser APIs.
Details: Use
@angular/elements. Use createCustomElement to bridge the Angular lifecycle to the native Custom Element specification.// In main.ts
import { createCustomElement } from '@angular/elements';
createApplication(appConfig).then(appRef => {
const el = createCustomElement(WidgetComp, { injector: appRef.injector });
customElements.define('my-widget', el);
});
Q84
Inject dependencies outside of constructors.
ExpertProblem: You need to use
inject() inside a vanilla JavaScript function that is executed outside of Angular's standard initialization cycle.Details: Use
runInInjectionContext. Pass it a reference to an active Injector to manually create an injection context environment.import { Injector, runInInjectionContext } from '@angular/core';
function executeLogic(injector: Injector) {
runInInjectionContext(injector, () => {
const service = inject(MyService);
service.process();
});
}
Q85
Set up a basic Web Worker.
ExpertProblem: Offload a massive CPU-intensive calculation (like processing millions of rows) away from the main UI thread.
Details: Generate a worker file using Angular CLI. Instantiate the worker pointing to that URL, post messages to it, and listen for the asynchronous result without blocking rendering.
// ng generate web-worker my-worker
if (typeof Worker !== 'undefined') {
const worker = new Worker(new URL('./app.worker', import.meta.url));
worker.onmessage = ({ data }) => console.log('Result:', data);
worker.postMessage('compute');
}
Q86
Dynamically swap CSS stylesheets at runtime.
ExpertProblem: Implement a full dark/light theme toggle that replaces the underlying application CSS file dynamically.
Details: Inject the
DOCUMENT token. Locate the <link rel="stylesheet"> tag in the head and mutate its href property directly.export class ThemeService {
private doc = inject(DOCUMENT);
setTheme(name: string) {
let link = this.doc.getElementById('theme-css') as HTMLLinkElement;
if (!link) {
link = this.doc.createElement('link');
link.id = 'theme-css'; link.rel = 'stylesheet';
this.doc.head.appendChild(link);
}
link.href = `${name}.css`;
}
}
Q87
Provide multiple values under one Injection Token.
ExpertProblem: Create an extensible plugin architecture where multiple distinct services register themselves under the same global token.
Details: Use
multi: true in the provider configuration. When a consumer injects the token, Angular resolves it into an array of all provided instances.// Providers:
{ provide: PLUGINS, useValue: PluginA, multi: true },
{ provide: PLUGINS, useValue: PluginB, multi: true }
// Consumer receives an array:
private plugins = inject(PLUGINS); // [PluginA, PluginB]
Q88
Handle Circular Dependencies.
ExpertProblem: Two classes reference each other, or a provider config needs to reference a class before it is technically defined in the file.
Details: Wrap the class reference in
forwardRef(). This tells Angular's dependency injection system to wait and evaluate the reference lazily.import { forwardRef } from '@angular/core';
@Component({
providers: [{
provide: ParentToken,
useExisting: forwardRef(() => ChildClass)
}]
})
export class ChildClass {}
Q89
Bypass the DomSanitizer safely.
ExpertProblem: Render raw HTML strings containing inline styles or scripts (like a trusted CMS output) which Angular strips by default.
Details: Inject
DomSanitizer and call bypassSecurityTrustHtml(). This disables XSS protection for that string—use only with fully trusted backend data.export class HtmlComp {
private sanitizer = inject(DomSanitizer);
safeHtml: SafeHtml;
setHtml(dirtyHtml: string) {
this.safeHtml = this.sanitizer.bypassSecurityTrustHtml(dirtyHtml);
}
}
Q90
Catch Unhandled RxJS errors globally.
ExpertProblem: Streams that error out without a local
catchError block crash the subscription silently; you need to log them.Details: Import the global
config object from rxjs. Assign a callback to onUnhandledError directly in your main.ts initialization file.import { config } from 'rxjs';
// Place in main.ts
config.onUnhandledError = (err) => {
console.error('Missed by catchError:', err);
};
Q91
Map routes conditionally based on screen size.
ExpertProblem: Serve a completely different component for the same
/dashboard URL based on whether the user is on mobile or desktop.Details: Create a custom
matcher function in the routing config. It examines custom logic (like `window.innerWidth`) and returns consumed segments if it matches.export function mobileMatch(url: UrlSegment[]) {
if (window.innerWidth < 768 && url[0].path === 'dash') {
return { consumed: url };
}
return null;
}
// Route config: { matcher: mobileMatch, component: MobileDashComp }
Q92
Extract raw DOM elements from an `ng-template`.
ExpertProblem: You have an
<ng-template> but need to programmatically access its raw HTML nodes to pass to a non-Angular charting library.Details: Use
ViewContainerRef.createEmbeddedView. It instantiates the template in memory, allowing you to access the raw DOM objects via view.rootNodes.@ViewChild('tpl') tpl!: TemplateRef<any>;
private vcr = inject(ViewContainerRef);
extract() {
const view = this.tpl.createEmbeddedView(null);
// view.rootNodes[0] is the raw DOM element
}
Q93
Prevent SSR chunks from hydrating.
ExpertProblem: Render a heavy static footer on the server, but completely prevent Angular from wasting CPU attaching event listeners to it on the client.
Details: Use the
@defer (hydrate never) block. It instructs the client hydration process to completely ignore the HTML block shipped by the server.<!-- Leaves it strictly as static HTML -->
@defer (hydrate never) {
<heavy-static-footer />
}
Q94
Inherit components cleanly without `super()`.
ExpertProblem: Create an abstract base class that injects 5 services, without forcing child classes to manually inject and pass them via
super(s1, s2...).Details: Use the
inject() function inside the abstract class properties. This completely decouples dependency injection from the class constructor hierarchy.export abstract class BaseComp {
protected api = inject(ApiService);
}
@Component({...})
export class ChildComp extends BaseComp {
doWork() { this.api.get(); } // Clean inheritance
}
Q95
Override an external library component's behavior.
ExpertProblem: You use a third-party UI library button, but need to automatically attach custom logic to every instance without modifying the library source.
Details: Create a standalone directive whose
selector exactly matches the third-party component's tag. Angular applies your directive alongside their component.@Directive({
selector: 'mat-button', // Hijacks Material Button
standalone: true
})
export class ButtonOverrideDirective {
constructor(private el: ElementRef) {
this.el.nativeElement.style.borderRadius = '8px';
}
}
Q96
Create an Angular Micro-frontend.
ExpertProblem: Expose a single Angular component so an entirely different Host application can load it over the network at runtime.
Details: Utilize Webpack 5 Module Federation in your build configuration to expose the compiled file mapped to a remote entry point.
// In webpack.config.js
plugins: [
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./Widget': './src/app/widget.component.ts',
},
shared: { '@angular/core': { singleton: true } }
})
]
Q97
Manage Complex State using NgRx Signal Store.
ExpertProblem: Create a highly performant, boilerplate-free state machine relying entirely on Signals instead of RxJS observables.
Details: Use
@ngrx/signals. It provides a functional signalStore API integrating state slices, computed values, and update methods seamlessly.import { signalStore, withState, withMethods } from '@ngrx/signals';
export const UserStore = signalStore(
{ providedIn: 'root' },
withState({ users: [] }),
withMethods(store => ({
add(user) { patchState(store, { users: [...store.users(), user] }); }
}))
);
Q98
Apply multiple directives to a component dynamically via metadata.
ExpertProblem: Apply reusable behavior (like tooltip and ripple effect directives) automatically when a specific component is used.
Details: Use the
hostDirectives property in the component decorator. It composes standalone directives directly onto the host element at compilation.@Component({
selector: 'app-special-button',
hostDirectives: [TooltipDirective, RippleDirective],
template: '<button>Hover Me</button>'
})
export class SpecialBtnComp {}
Q99
Safely mutate DOM elements without direct native Element access.
ExpertProblem: You must append a class to a DOM node, but
nativeElement.classList.add causes issues in Service Workers or SSR.Details: Inject
Renderer2. It abstracts DOM manipulations so they execute safely across all host environments (Browser, Server, Web Worker).export class SafeComp {
private renderer = inject(Renderer2);
private el = inject(ElementRef);
modify() {
this.renderer.addClass(this.el.nativeElement, 'safe-class');
}
}
Q100
Fix ExpressionChangedAfterItHasBeenCheckedError gracefully.
ExpertProblem: A child component updates a parent's bound variable immediately during the initialization cycle, causing Angular's unidirectional data flow check to crash.
Details: You must defer the update until the next JavaScript macro/micro task so the current Change Detection tick finishes first. Wrap it in a resolved Promise.
export class ChildComp implements OnInit {
@Output() ready = new EventEmitter<boolean>();
ngOnInit() {
// Defers emit until current synchronous CD finishes
Promise.resolve().then(() => this.ready.emit(true));
}
}