codejump

Interview questionsAngular

Angular interview questions and answers

30 Angular questions picked from Codejump's quizzes, from the basics a junior is expected to know to the details that trip up senior candidates. Each one comes with its answer and an explanation — try to answer before you open it.

Junior level Angular questions

  1. 1.How do you listen to a button click event in an Angular template?

    javascript
    @Component({
      selector: 'app-counter',
      template: `
        <p>Count: {{ count }}</p>
        <button (click)="increment()">+1</button>
      `,
      standalone: true,
    })
    export class CounterComponent {
      count = 0;
      increment() { this.count++; }
    }
    • A.[click]="handleClick()"
    • B.{{click}}="handleClick()"
    • C.*click="handleClick()"
    • D.(click)="handleClick()"
    Show answer

    Answer: D. (click)="handleClick()"

    Parentheses syntax (event)="handler()" is Angular's event binding. It listens to the specified DOM event and calls the handler method on the component. You can also pass the event object: (click)="handleClick($event)".

    From the quiz Component Basics

  2. 2.How do you inject a service into an Angular component?

    javascript
    @Component({
      selector: 'app-profile',
      template: `<p>{{ user?.name }}</p>`,
      standalone: true,
    })
    export class ProfileComponent implements OnInit {
      user?: User;
    
      constructor(
        private userService: UserService,
        private router: Router,
      ) {}
    
      ngOnInit() {
        this.userService.getCurrentUser().subscribe(u => this.user = u);
      }
    }
    • A.By declaring the service type in the constructor parameters
    • B.By importing the service module using ES6 imports only
    • C.By calling ServiceClass.getInstance() in the component
    • D.By using @Inject(ServiceClass) in the template
    Show answer

    Answer: A. By declaring the service type in the constructor parameters

    Angular uses constructor injection — services are provided via the constructor's parameter list. Angular reads the TypeScript types (via Reflect metadata) to know what to inject. In Angular 14+, you can also use the inject() function as an alternative:

    From the quiz Injectable & Providers

  3. 3.How does a child component communicate with its parent?

    javascript
    // child.component.ts
    @Component({
      selector: 'app-counter',
      template: `
        <p>Count: {{ count }}</p>
        <button (click)="increment()">+</button>
      `
    })
    export class CounterComponent {
      @Input() count = 0;
      @Output() countChange = new EventEmitter<number>();
    
      increment() {
        this.countChange.emit(this.count + 1);
      }
    }
    
    // parent template
    <app-counter [count]="myCount" (countChange)="myCount = $event" />
    • A.@Input() receives data from parent to child; @Output() emits events from child to parent via EventEmitter
    • B.@Input() and @Output() are optional — Angular automatically detects public properties
    • C.@Output() only works with EventEmitter<void>
    • D.The parent must use a shared service — it cannot listen to the child's events directly
    Show answer

    Answer: A. @Input() receives data from parent to child; @Output() emits events from child to parent via EventEmitter

    Modern Angular 17.1+ with model():

    From the quiz Angular Basics

  4. 4.What is the purpose of NgRx selectors?

    javascript
    import { createSelector, createFeatureSelector } from '@ngrx/store';
    
    // Feature selector — selects the 'users' slice of root state
    export const selectUsersState = createFeatureSelector<UsersState>('users');
    
    // Derived selectors — memoized
    export const selectAllUsers = createSelector(
      selectUsersState,
      state => state.users
    );
    
    export const selectUsersLoading = createSelector(
      selectUsersState,
      state => state.loading
    );
    
    export const selectActiveUsers = createSelector(
      selectAllUsers,
      users => users.filter(u => u.active)  // only re-computed when selectAllUsers changes
    );
    
    // Component usage:
    @Component({ standalone: true })
    export class UsersComponent {
      users$ = inject(Store).select(selectAllUsers);
      loading$ = inject(Store).select(selectUsersLoading);
      activeUsers$ = inject(Store).select(selectActiveUsers);
    }
    • A.To dispatch actions to the store
    • B.To define what state slices reducers manage
    • C.To derive and memoize specific slices of state for components to consume
    • D.To configure which components receive store updates
    Show answer

    Answer: C. To derive and memoize specific slices of state for components to consume

    Selectors are memoized pure functions that select and derive data from the store: Selectors are memoized — if the input state slice didn't change, the function is not re-run and the previous result is returned.

    From the quiz Actions & Reducers

  5. 5.How do you access a route parameter (e.g., /users/:id) in a component?

    javascript
    @Component({
      selector: 'app-user-detail',
      template: `<p>User ID: {{ userId }}</p>`,
      standalone: true,
    })
    export class UserDetailComponent implements OnInit {
      userId?: string;
    
      // Option 1: snapshot — one-time read (use when component is not reused for same route)
      private route = inject(ActivatedRoute);
    
      ngOnInit() {
        this.userId = this.route.snapshot.params['id'];
    
        // Option 2: Observable — reactive (use when navigating between same route with different params)
        this.route.params.subscribe(params => {
          this.userId = params['id'];
          this.loadUser(this.userId!);
        });
    
        // Option 3: Signal (Angular 16+)
        this.userId = toSignal(this.route.params.pipe(map(p => p['id'])), { initialValue: '' })();
      }
    }
    • A.Via this.route.params which is a regular object with parameter values
    • B.Via @Input() id: string automatically bound by the router
    • C.Via this.router.params.id directly on the Router service
    • D.Via inject(ActivatedRoute).snapshot.params for one-time access, or inject(ActivatedRoute).params Observable for reactive access
    Show answer

    Answer: D. Via inject(ActivatedRoute).snapshot.params for one-time access, or inject(ActivatedRoute).params Observable for reactive access

    Route parameters are available via ActivatedRoute: In Angular 16+ with withComponentInputBinding(), route params are automatically bound to @Input():

    From the quiz Route Config & Links

  6. 6.What does HttpClient.get<User[]>('/api/users') return?

    javascript
    @Injectable({ providedIn: 'root' })
    class UserService {
      constructor(private http: HttpClient) {}
    
      getUsers(): Observable<User[]> {
        // No request yet — just a definition
        return this.http.get<User[]>('/api/users');
      }
    }
    
    @Component({ ... })
    class UserListComponent implements OnInit {
      users: User[] = [];
    
      constructor(private userService: UserService) {}
    
      ngOnInit() {
        // Request fires HERE when subscribe() is called
        this.userService.getUsers().subscribe(users => {
          this.users = users;
        });
    
        // Or let async pipe handle it:
        this.users$ = this.userService.getUsers();  // no request yet
      }
    }
    • A.A Promise<User[]>
    • B.An Observable<User[]> that makes the HTTP request only when subscribed
    • C.A User[] array directly
    • D.A Subject<User[]> that replays the response to late subscribers
    Show answer

    Answer: B. An Observable<User[]> that makes the HTTP request only when subscribed

    All HttpClient methods return cold Observables — the HTTP request is only made when you subscribe() (or use async pipe).

    From the quiz Observables & HTTP

  7. 7.What does the selector property of @Component define?

    javascript
    @Component({
      selector: 'app-card',      // used as <app-card></app-card>
      template: `<div class="card"><ng-content></ng-content></div>`,
      standalone: true,
    })
    export class CardComponent {}
    • A.The CSS file to apply to the component
    • B.The name used to embed the component in other templates
    • C.The route path of the component
    • D.The Angular module that owns the component
    Show answer

    Answer: B. The name used to embed the component in other templates

    The selector is the custom HTML element name (or CSS selector) used to insert the component into templates. When Angular parses a template and finds a matching selector, it renders the component there. In another template: <app-card>Hello</app-card> Selectors can also be attribute-based ([appCard]) or class-based (.app-card), but element selectors are the most common.

    From the quiz Component Basics

  8. 8.What is the inject() function and when can it be used?

    javascript
    @Component({
      selector: 'app-dashboard',
      template: `...`,
      standalone: true,
    })
    export class DashboardComponent {
      // Works in field initializers (injection context is active)
      private userService = inject(UserService);
      private router = inject(Router);
      private analyticsService = inject(AnalyticsService, { optional: true });
    
      // Constructor is optional — can be omitted entirely
    }
    • A.It is the same as @Inject() decorator but for standalone components only
    • B.It replaces the constructor entirely and cannot be combined with constructor injection
    • C.It is used only inside @NgModule providers for forward references
    • D.It retrieves a dependency from the current injection context — usable in constructor, field initializers, and factory functions
    Show answer

    Answer: D. It retrieves a dependency from the current injection context — usable in constructor, field initializers, and factory functions

    inject() is a functional API for dependency injection that works in any injection context (constructor, class field initializers, runInInjectionContext, etc.). inject() also works in composable functions (similar to React hooks):

    From the quiz Injectable & Providers

  9. 9.What is the correct syntax for the control flow introduced in Angular 17?

    javascript
    @Component({
      template: `
        <!-- Conditional rendering -->
        @if (isLoggedIn) {
          <p>Welcome, {{ username }}!</p>
        } @else {
          <p>Please sign in.</p>
        }
    
        <!-- List rendering -->
        @for (item of items; track item.id) {
          <li>{{ item.name }}</li>
        } @empty {
          <li>No items found.</li>
        }
      `
    })
    • A.This syntax is invalid — Angular only uses *ngIf and *ngFor
    • B.@else is not supported — you must use two separate @if blocks with inverse conditions
    • C.track in @for is optional and has no effect on performance
    • D.@if / @for is the new built-in control flow syntax in Angular 17+, replacing *ngIf and *ngFor. It does not require importing CommonModule or NgIf/NgFor
    Show answer

    Answer: D. @if / @for is the new built-in control flow syntax in Angular 17+, replacing *ngIf and *ngFor. It does not require importing CommonModule or NgIf/NgFor

    Angular 17 introduced built-in control flow as a language-level syntax — no directives, no imports needed: track is REQUIRED in @for — it identifies each item for DOM reconciliation (like React's key). Using track $index is allowed but track item.id is preferred for stable identity.

    From the quiz Angular Basics

  10. 10.How do you read state from the store in a component?

    javascript
    @Component({
      selector: 'app-cart',
      template: `
        @if (loading$ | async) {
          <p>Loading...</p>
        } @else {
          @for (item of items$ | async; track item.id) {
            <app-cart-item [item]="item" />
          }
        }
      `,
      standalone: true,
      imports: [AsyncPipe, CartItemComponent],
    })
    export class CartComponent {
      private store = inject(Store);
    
      items$ = this.store.select(selectCartItems);
      loading$ = this.store.select(selectCartLoading);
      totalPrice$ = this.store.select(selectCartTotalPrice);
    
      removeItem(itemId: string) {
        this.store.dispatch(removeFromCart({ itemId }));
      }
    }
    • A.const state = Store.getState()
    • B.Subscribe to StoreEvents.onChange in ngOnInit
    • C.inject(Store).state which is a plain object
    • D.inject(Store).select(selector) which returns an Observable
    Show answer

    Answer: D. inject(Store).select(selector) which returns an Observable

    Store.select() returns an Observable of the selected state slice, updated whenever the relevant state changes:

    From the quiz Actions & Reducers

Mid level Angular questions

  1. 11.What does @ViewChild(ChildComponent) give you access to?

    javascript
    @Component({
      selector: 'app-parent',
      template: `<app-timer #timer />`,
      standalone: true,
      imports: [TimerComponent],
    })
    export class ParentComponent implements AfterViewInit {
      @ViewChild(TimerComponent) timer!: TimerComponent;
      // OR by template ref: @ViewChild('timer') timer!: TimerComponent;
    
      ngAfterViewInit() {
        this.timer.start();  // ✅ available here
      }
    }
    • A.The HTML element wrapping the child component
    • B.All matching child component instances in the view
    • C.The first matching child component instance in the view
    • D.The child component's @Input() declarations
    Show answer

    Answer: C. The first matching child component instance in the view

    @ViewChild queries the component's view (its own template) and returns the first matching element, directive, or component instance. It is available after ngAfterViewInit. To query all matching children, use @ViewChildren, which returns a QueryList<T>.

    From the quiz Lifecycle & Projection

  2. 12.What problem does the forRoot() / forChild() pattern solve?

    javascript
    @NgModule()
    export class AuthModule {
      static forRoot(config: AuthConfig): ModuleWithProviders<AuthModule> {
        return {
          ngModule: AuthModule,
          providers: [
            AuthService,           // singleton at root level
            { provide: AUTH_CONFIG, useValue: config },
          ],
        };
      }
    
      static forChild(): ModuleWithProviders<AuthModule> {
        return {
          ngModule: AuthModule,
          providers: [],           // no services — reuse root singleton
        };
      }
    }
    
    // AppModule
    @NgModule({ imports: [AuthModule.forRoot({ realm: 'codejump' })] })
    export class AppModule {}
    
    // FeatureModule (lazy)
    @NgModule({ imports: [AuthModule.forChild()] })
    export class ProfileModule {}
    • A.It sets up routing for the root module
    • B.It allows a module to be used in both eager and lazy-loaded contexts with correct service scoping
    • C.It enables tree-shaking for NgModule providers
    • D.It registers component declarations for both root and child routes
    Show answer

    Answer: B. It allows a module to be used in both eager and lazy-loaded contexts with correct service scoping

    forRoot() provides services and configuration at the root level (singleton), while forChild() provides only the directives/pipes/components for child feature modules, without re-providing services (which would create duplicate instances). In standalone apps, this pattern is less common — use provideAuth(config) functions with ApplicationConfig.

    From the quiz Hierarchical Injectors

  3. 13.How do you configure routing in a standalone Angular 19 application?

    javascript
    // app.routes.ts
    export const routes: Routes = [
      { path: '', redirectTo: '/home', pathMatch: 'full' },
      { path: 'home', component: HomeComponent },
      {
        path: 'users',
        loadComponent: () => import('./users/users.component').then(m => m.UsersComponent),
      },
      { path: 'users/:id', component: UserDetailComponent },
      { path: '**', component: NotFoundComponent },
    ];
    
    // app.config.ts
    export const appConfig: ApplicationConfig = {
      providers: [provideRouter(routes)],
    };
    • A.loadComponent is not a valid Routes property — use loadChildren instead
    • B.The configuration is correct. loadComponent lazy-loads a standalone component; path: '**' is the wildcard route for 404s; route order matters (most specific first)
    • C.pathMatch: 'full' is the default value — there's no need to specify it
    • D.Route parameters like :id require an additional params: configuration
    Show answer

    Answer: B. The configuration is correct. loadComponent lazy-loads a standalone component; path: '**' is the wildcard route for 404s; route order matters (most specific first)

    Route order matters: Angular matches routes top-to-bottom. path: '**' must be LAST. ---

    From the quiz Templates & Binding

  4. 14.What does on() inside createReducer do?

    javascript
    import { createReducer, on } from '@ngrx/store';
    
    export const cartReducer = createReducer(
      initialCartState,
    
      // Single action handler
      on(addToCart, (state, { item }) => ({
        ...state,
        items: [...state.items, item],
      })),
    
      // Multiple actions with same handler
      on(clearCart, resetCart, state => ({
        ...state,
        items: [],
        total: 0,
      })),
    
      // Handler with complex logic
      on(applyDiscount, (state, { code, percentage }) => {
        const discountedTotal = state.subtotal * (1 - percentage / 100);
        return {
          ...state,
          coupon: code,
          discount: percentage,
          total: discountedTotal,
        };
      }),
    );
    • A.Subscribes to an Observable for each action type
    • B.Runs the provided function asynchronously when the action is dispatched
    • C.Creates a new action and links it to a reducer handler
    • D.Registers a state transformation function for one or more action types
    Show answer

    Answer: D. Registers a state transformation function for one or more action types

    on(action, handler) registers a handler function that computes new state when the specified action is dispatched: Each on() call corresponds to one or more action types. When an action is dispatched, NgRx finds the matching on() handlers and applies them in order.

    From the quiz Effects & Selectors

  5. 15.What is the purpose of a route resolver?

    javascript
    // user.resolver.ts
    export const userResolver: ResolveFn<User> = (route) => {
      const userService = inject(UserService);
      const router = inject(Router);
    
      return userService.getUser(route.params['id']).pipe(
        catchError(() => {
          router.navigate(['/not-found']);
          return EMPTY;
        }),
      );
    };
    
    // Route config:
    {
      path: 'users/:id',
      component: UserDetailComponent,
      resolve: { user: userResolver },
    }
    
    // Component — data is available synchronously:
    @Component({ standalone: true })
    export class UserDetailComponent implements OnInit {
      private route = inject(ActivatedRoute);
      user!: User;
    
      ngOnInit() {
        this.user = this.route.snapshot.data['user'];
        // OR with Angular 16+ withComponentInputBinding():
        // @Input() user!: User;  // auto-bound from resolver
      }
    }
    • A.To pre-fetch data before the route's component is created, making the data available immediately in the component
    • B.To validate URL parameters before the route activates
    • C.To transform the URL before the router processes it
    • D.To determine which component to render based on screen size
    Show answer

    Answer: A. To pre-fetch data before the route's component is created, making the data available immediately in the component

    Resolvers pre-fetch data during navigation so the component receives it immediately via ActivatedRoute.data:

    From the quiz Guards & Lazy Loading

  6. 16.What does ReplaySubject(3) do?

    javascript
    import { ReplaySubject } from 'rxjs';
    
    const replay$ = new ReplaySubject<string>(3);
    
    replay$.next('a');
    replay$.next('b');
    replay$.next('c');
    replay$.next('d');  // 'a' is dropped from buffer (buffer size is 3)
    
    // New subscriber immediately receives buffered values:
    replay$.subscribe(v => console.log('New subscriber:', v));
    // "New subscriber: b"
    // "New subscriber: c"
    // "New subscriber: d"
    
    // Useful for caching with shareReplay:
    const cachedUsers$ = this.http.get<User[]>('/api/users').pipe(
      shareReplay(1),  // ReplaySubject(1) under the hood
    );
    // All subscribers get the same cached response
    • A.Buffers the last 3 emitted values and replays all of them to each new subscriber
    • B.Replays the last 3 milliseconds of emissions to new subscribers
    • C.Limits the stream to only 3 total emissions before completing
    • D.Creates a Subject that shares emissions between 3 subscribers maximum
    Show answer

    Answer: A. Buffers the last 3 emitted values and replays all of them to each new subscriber

    ReplaySubject(n) buffers the last n emissions and replays them synchronously to any new subscriber.

    From the quiz Operators & Subjects

  7. 17.What does <ng-content> do inside a component's template?

    javascript
    @Component({
      selector: 'app-card',
      template: `
        <div class="card">
          <ng-content select="[card-header]" />
          <div class="body">
            <ng-content />
          </div>
        </div>
      `,
      standalone: true,
    })
    export class CardComponent {}
    
    // Usage:
    // <app-card>
    //   <h2 card-header>Title</h2>
    //   <p>Body content here</p>
    // </app-card>
    • A.It fetches content from an HTTP endpoint
    • B.It marks the location for router-outlet rendering
    • C.It creates a portal for modal dialogs
    • D.It acts as a slot that renders the content passed between the component's opening and closing tags
    Show answer

    Answer: D. It acts as a slot that renders the content passed between the component's opening and closing tags

    <ng-content> is Angulars content projection mechanism, similar to web component slots. The content defined between the components tags in the parent is projected into the <ng-content> slot. The select attribute enables multi-slot projection, allowing different pieces of projected content to land in different slots.

    From the quiz Lifecycle & Projection

  8. 18.When Angular looks up a dependency, what is the order it searches injectors?

    javascript
    Element injector (component/directive providers)
      → Parent element injectors (up the component tree)
        → Module injector (for NgModule-based apps)
          → Root injector (providedIn: 'root')
            → Platform injector
              → Null injector (throws NullInjectorError)
    • A.Component injector → Parent component injectors → Module injector → Root injector → Platform injector → Null injector
    • B.Root injector → Platform injector → Null injector
    • C.Service file → Root injector → Component injector
    • D.NgModule injector → Component injector → Root injector
    Show answer

    Answer: A. Component injector → Parent component injectors → Module injector → Root injector → Platform injector → Null injector

    Angular has a hierarchical injector tree. When a token is not found, Angular walks up the tree:

    From the quiz Hierarchical Injectors

  9. 19.Why use lazy loading and how does it differ from eager loading?

    javascript
    const routes: Routes = [
      // Eager loading — bundled in main chunk:
      { path: 'home', component: HomeComponent },
    
      // Lazy loading — separate chunk downloaded on demand:
      {
        path: 'dashboard',
        loadComponent: () =>
          import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
      },
      {
        path: 'admin',
        loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes),
      },
    ];
    • A.Lazy loading slows navigation down — Angular must wait for the chunk to download before it can render the route
    • B.loadComponent and loadChildren are identical
    • C.Lazy loading shrinks the initial bundle (faster time-to-interactive) by splitting the app into on-demand chunks. Navigation is slightly delayed on first access, but Angular can preload the chunks in the background
    • D.Lazy loading disables the route guard on the affected route
    Show answer

    Answer: C. Lazy loading shrinks the initial bundle (faster time-to-interactive) by splitting the app into on-demand chunks. Navigation is slightly delayed on first access, but Angular can preload the chunks in the background

    loadComponent vs loadChildren:
    • loadComponent — lazy-loads a single standalone component
    • loadChildren — lazy-loads a set of routes (previously used for NgModules, now for route arrays) ---

    From the quiz Templates & Binding

  10. 20.What does the NgRx Store DevTools extension enable?

    javascript
    // Setup:
    provideStoreDevtools({
      maxAge: 25,                      // retain last 25 actions
      logOnly: !isDevMode(),           // disable DevTools in production
      autoPause: true,                 // pause recording when not in use
      trace: false,                    // include stack trace
      traceLimit: 75,                  // limit stack trace lines
      connectInZone: true,             // ensure CD after DevTools actions
    })
    
    // Features:
    // 1. Action log: see every dispatched action with its payload
    // 2. State diff: see exactly what changed after each action
    // 3. Time-travel: jump back to any previous state
    // 4. Replay: re-dispatch actions from a specific point
    // 5. Import/Export: save and load action logs for bug reports
    // 6. Action filtering: filter the action log for debugging
    • A.Visual profiling of Angular change detection cycles
    • B.HTTP request monitoring for Effects
    • C.Automatic unit test generation for NgRx actions and reducers
    • D.Time-travel debugging — inspecting dispatched actions and state snapshots, replaying/rewinding actions
    Show answer

    Answer: D. Time-travel debugging — inspecting dispatched actions and state snapshots, replaying/rewinding actions

    NgRx DevTools (requires Redux DevTools browser extension) provides: Pair with NgRx Router Store to see navigation as actions:

    From the quiz Effects & Selectors

Senior level Angular questions

  1. 21.What is the difference between a pure pipe and an impure pipe?

    javascript
    // Pure pipe (default) — only re-runs when input reference changes:
    @Pipe({ name: 'filter', standalone: true })
    export class FilterPipe implements PipeTransform {
      transform(items: Item[], query: string): Item[] {
        return items.filter(item => item.name.includes(query));
      }
    }
    
    // Impure pipe — re-runs on every change detection cycle:
    @Pipe({ name: 'filterImpure', pure: false, standalone: true })
    export class FilterImpurePipe implements PipeTransform {
      transform(items: Item[], query: string): Item[] {
        return items.filter(item => item.name.includes(query));
      }
    }
    • A.Impure pipes are faster because they don't check references
    • B.pure: false is required for pipes that return Observables
    • C.Pure pipes are cached — they only re-run when the input's reference changes (an optimization). Impure pipes run on every change-detection cycle — necessary for mutated arrays/objects but expensive
    • D.Pure pipes don't work with multiple arguments
    Show answer

    Answer: C. Pure pipes are cached — they only re-run when the input's reference changes (an optimization). Impure pipes run on every change-detection cycle — necessary for mutated arrays/objects but expensive

    ---

    From the quiz Advanced Angular

  2. 22.How do you create a custom structural directive like *appUnless?

    javascript
    @Directive({
      selector: '[appUnless]',
      standalone: true,
    })
    export class UnlessDirective {
      private hasView = false;
    
      constructor(
        private templateRef: TemplateRef<unknown>,
        private viewContainer: ViewContainerRef
      ) {}
    
      @Input() set appUnless(condition: boolean) {
        if (!condition && !this.hasView) {
          this.viewContainer.createEmbeddedView(this.templateRef);
          this.hasView = true;
        } else if (condition && this.hasView) {
          this.viewContainer.clear();
          this.hasView = false;
        }
      }
    }
    
    // Usage: <div *appUnless="isLoggedIn">Please sign in</div>
    • A.By extending NgIf and overriding its condition logic
    • B.By using @HostBinding with the hidden attribute
    • C.By injecting TemplateRef and ViewContainerRef, then calling createEmbeddedView or clear
    • D.By returning null from the template expression
    Show answer

    Answer: C. By injecting TemplateRef and ViewContainerRef, then calling createEmbeddedView or clear

    Structural directives use TemplateRef (the template to stamp) and ViewContainerRef (where to stamp it).

    From the quiz Dynamic Components

  3. 23.What pattern enables Angular "composable" functions similar to React hooks?

    javascript
    // composables/use-current-user.ts
    export function useCurrentUser(): Signal<User | null> {
      const authService = inject(AuthService);
      const userService = inject(UserService);
    
      return computed(() => {
        const token = authService.token();
        return token ? userService.getUserFromToken(token) : null;
      });
    }
    
    // composables/use-page-title.ts
    export function usePageTitle(title: string | Signal<string>): void {
      const titleService = inject(Title);
      const appName = inject(APP_NAME);
    
      effect(() => {
        const resolvedTitle = typeof title === 'string' ? title : title();
        titleService.setTitle(`${resolvedTitle} | ${appName}`);
      });
    }
    
    // Usage in component
    @Component({ ... })
    export class ProfileComponent {
      // Called during field initialization = injection context ✅
      currentUser = useCurrentUser();
      constructor() { usePageTitle('Profile'); }
    }
    • A.Extending Injectable with a use prefix naming convention
    • B.Using @Directive with exportAs to expose service instances
    • C.Writing functions that call inject() and can be called during component/directive construction (injection context)
    • D.Creating wrapper classes with static getInstance() methods
    Show answer

    Answer: C. Writing functions that call inject() and can be called during component/directive construction (injection context)

    Functions that call inject() internally must be called during an injection context (constructor or field initializer). This enables powerful composable patterns:

    From the quiz Standalone & Tokens

  4. 24.How do you implement optimistic updates with NgRx?

    javascript
    // Actions
    export const updateTodo = createAction('[Todo] Update Todo', props<{ todo: Todo }>());
    export const updateTodoSuccess = createAction('[Todo API] Update Todo Success', props<{ todo: Todo }>());
    export const updateTodoFailure = createAction('[Todo API] Update Todo Failure', props<{ originalTodo: Todo; error: string }>());
    
    // Reducer — apply immediately
    on(updateTodo, (state, { todo }) =>
      todoAdapter.upsertOne(todo, state)        // ← optimistic update
    ),
    on(updateTodoSuccess, (state, { todo }) =>
      todoAdapter.upsertOne(todo, state)        // confirm with server version
    ),
    on(updateTodoFailure, (state, { originalTodo }) =>
      todoAdapter.upsertOne(originalTodo, state) // revert to original
    ),
    
    // Effect — capture originalTodo (pre-optimistic value) for rollback.
    // concatLatestFrom reads the store lazily, when the action arrives.
    updateTodo$ = createEffect(() =>
      this.actions$.pipe(
        ofType(updateTodo),
        concatLatestFrom(({ todo }) => this.store.select(selectTodoById(todo.id))),
        concatMap(([{ todo }, originalTodo]) =>
          this.todoService.update(todo).pipe(
            map(updated => updateTodoSuccess({ todo: updated })),
            catchError(error =>
              of(updateTodoFailure({ originalTodo: originalTodo!, error: error.message }))
            ),
          )
        ),
      )
    );
    • A.Wait for the API response before dispatching any state changes
    • B.Update the store immediately on dispatch, and revert on API failure
    • C.Use @ngrx/entity adapter's optimistic mode
    • D.Dispatch a pending action and block UI until confirmation
    Show answer

    Answer: B. Update the store immediately on dispatch, and revert on API failure

    Optimistic updates improve perceived performance by updating the UI before the API confirms:

    From the quiz Component Store

  5. 25.What does the RouterStateSnapshot contain?

    javascript
    export const loggingGuard: CanActivateFn = (route, state) => {
      // state: RouterStateSnapshot
      console.log('Navigating to:', state.url);
    
      // Traverse the route tree:
      let node: ActivatedRouteSnapshot | null = state.root;
      while (node) {
        console.log({
          path: node.routeConfig?.path,
          params: node.params,
          queryParams: node.queryParams,
          data: node.data,
        });
        node = node.firstChild;
      }
    
      return true;
    };
    
    // Getting current state in a service:
    @Injectable({ providedIn: 'root' })
    class RouteStateService {
      private router = inject(Router);
    
      getCurrentParams(): Record<string, string> {
        let route = this.router.routerState.snapshot.root;
        const params: Record<string, string> = {};
    
        while (route.firstChild) {
          Object.assign(params, route.params);
          route = route.firstChild;
        }
    
        return { ...params, ...route.params };
      }
    }
    • A.Only the current URL as a string
    • B.A tree of ActivatedRouteSnapshot nodes representing the full current router state (all active routes, their params, data, and segments)
    • C.A history of previous navigation events
    • D.The resolved data for all active routes combined into one flat object
    Show answer

    Answer: B. A tree of ActivatedRouteSnapshot nodes representing the full current router state (all active routes, their params, data, and segments)

    RouterStateSnapshot is a snapshot of the full router state at a point in time:

    From the quiz Functional Guards

  6. 26.What is the difference between bufferTime and windowTime?

    javascript
    import { interval } from 'rxjs';
    import { bufferTime, windowTime, mergeAll, map } from 'rxjs/operators';
    
    // bufferTime — collects into arrays and emits arrays
    interval(100).pipe(
      bufferTime(500),  // collect emissions for 500ms, then emit array
    ).subscribe(batch => {
      console.log('Batch:', batch);  // [0,1,2,3,4] (5 values per 500ms)
    });
    
    // windowTime — emits Observables (windows)
    interval(100).pipe(
      windowTime(500),  // emit a new Observable every 500ms
      mergeMap(window$ => window$.pipe(
        reduce((acc, v) => acc + v, 0),  // sum each window
      )),
    ).subscribe(windowSum => {
      console.log('Window sum:', windowSum);
    });
    
    // bufferTime for batching API calls (debounce + batch pattern):
    userActions$.pipe(
      bufferTime(200),
      filter(actions => actions.length > 0),
      switchMap(actions => this.api.batchProcess(actions)),
    ).subscribe();
    • A.bufferTime uses fixed windows; windowTime uses sliding windows
    • B.bufferTime collects emissions into arrays; windowTime groups them into sub-Observables
    • C.windowTime is for time-based filtering; bufferTime is for count-based batching
    • D.They are identical — windowTime is the newer API
    Show answer

    Answer: B. bufferTime collects emissions into arrays; windowTime groups them into sub-Observables

    Both batch emissions over time, but differently:

    From the quiz Higher-Order Observables

  7. 27.What are ng-container and ng-template used for?

    javascript
    @Component({
      template: `
        <!-- ng-container: grouping without extra DOM element -->
        <ng-container *ngIf="isAdmin">
          <button>Edit</button>
          <button>Delete</button>
        </ng-container>
    
        <!-- ng-template: reusable template block -->
        <ng-template #loadingTpl>
          <div class="spinner">Loading…</div>
        </ng-template>
    
        @if (isLoading) {
          <ng-container *ngTemplateOutlet="loadingTpl" />
        }
      `
    })
    • A.ng-container is a grouping element that adds no node to the real DOM — useful for applying directives across several elements. ng-template defines a reusable template block that is not rendered by default
    • B.ng-container creates an invisible <div> in the DOM
    • C.ng-template and ng-container are interchangeable
    • D.ngTemplateOutlet requires CommonModule even in standalone components
    Show answer

    Answer: A. ng-container is a grouping element that adds no node to the real DOM — useful for applying directives across several elements. ng-template defines a reusable template block that is not rendered by default

    ---

    From the quiz Advanced Angular

  8. 28.What happens to a component's change detection when it uses a signal() as its state?

    javascript
    @Component({
      selector: 'app-counter',
      template: `<p>Count: {{ count() }}</p><button (click)="inc()">+1</button>`,
      standalone: true,
      changeDetection: ChangeDetectionStrategy.OnPush,
    })
    export class CounterComponent {
      count = signal(0);
    
      inc() {
        this.count.update(c => c + 1);
      }
    }
    • A.Only the components that read the signal are scheduled for re-render when the signal changes
    • B.Change detection runs on every signal write, globally
    • C.Signals bypass Angular's change detection entirely
    • D.The component must be manually subscribed to the signal using effect()
    Show answer

    Answer: A. Only the components that read the signal are scheduled for re-render when the signal changes

    Angular Signals introduce fine-grained reactivity. When a component template reads a signal, Angular tracks that dependency. When the signal changes, only that component (and its ancestors up to the first OnPush boundary) is marked dirty and re-rendered. With signals and OnPush, Angular can skip entire subtrees of the component tree that haven't changed, enabling very efficient rendering.

    From the quiz Dynamic Components

  9. 29.What is DestroyRef and how does it improve over ngOnDestroy?

    javascript
    // utility function — no need for the consumer to manage cleanup
    export function autoRefresh(intervalMs: number): void {
      const destroyRef = inject(DestroyRef);
      const router = inject(Router);
    
      const id = setInterval(() => router.navigate(['/refresh']), intervalMs);
    
      destroyRef.onDestroy(() => clearInterval(id));
    }
    
    // Service — cleanup tied to the injector's lifetime
    @Injectable()
    class WebSocketService {
      private ws: WebSocket;
    
      constructor(destroyRef: DestroyRef) {
        this.ws = new WebSocket('wss://api.example.com');
        destroyRef.onDestroy(() => this.ws.close());
      }
    }
    
    // takeUntilDestroyed operator uses DestroyRef internally
    import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
    
    @Component({ ... })
    class FeedComponent {
      private feedService = inject(FeedService);
    
      ngOnInit() {
        this.feedService.getFeed()
          .pipe(takeUntilDestroyed())  // auto-completes when component is destroyed
          .subscribe(items => this.items = items);
      }
    }
    • A.A built-in service that automatically destroys all component dependencies
    • B.A replacement for ChangeDetectorRef that controls component destruction timing
    • C.An injectable that allows any code (not just components implementing OnDestroy) to register cleanup callbacks tied to a component or injector's lifecycle
    • D.A token used to signal the destruction of lazy-loaded modules
    Show answer

    Answer: C. An injectable that allows any code (not just components implementing OnDestroy) to register cleanup callbacks tied to a component or injector's lifecycle

    DestroyRef (Angular 16+) decouples cleanup logic from ngOnDestroy, enabling composable cleanup in services, utilities, and functions.

    From the quiz Standalone & Tokens

  10. 30.What is a meta-reducer in NgRx?

    javascript
    import { MetaReducer, ActionReducer } from '@ngrx/store';
    import { localStorageSync } from 'ngrx-store-localstorage';
    
    // Meta-reducer 1: Logging
    function loggerMetaReducer(reducer: ActionReducer<any>): ActionReducer<any> {
      return (state, action) => {
        console.group(action.type);
        console.log('%c prev state', 'color: #9E9E9E', state);
        console.log('%c action', 'color: #03A9F4', action);
        const nextState = reducer(state, action);
        console.log('%c next state', 'color: #4CAF50', nextState);
        console.groupEnd();
        return nextState;
      };
    }
    
    // Meta-reducer 2: State hydration from localStorage
    function hydrationMetaReducer(reducer: ActionReducer<AppState>): ActionReducer<AppState> {
      return (state, action) => {
        if (action.type === INIT || action.type === UPDATE) {
          const storageValue = localStorage.getItem('app_state');
          if (storageValue) {
            try {
              return JSON.parse(storageValue);
            } catch { /* invalid JSON */ }
          }
        }
        const nextState = reducer(state, action);
        localStorage.setItem('app_state', JSON.stringify(nextState));
        return nextState;
      };
    }
    
    // Register in provideStore:
    export const metaReducers: MetaReducer<AppState>[] = isDevMode()
      ? [loggerMetaReducer, hydrationMetaReducer]
      : [hydrationMetaReducer];
    
    provideStore(reducers, { metaReducers })
    • A.A higher-order function that wraps a reducer with additional behavior — called before the reducer, able to transform actions or state
    • B.A reducer that manages state for multiple features simultaneously
    • C.A reducer factory that generates CRUD reducers automatically
    • D.A NgRx Effect that doubles as a reducer
    Show answer

    Answer: A. A higher-order function that wraps a reducer with additional behavior — called before the reducer, able to transform actions or state

    Meta-reducers wrap reducers to add cross-cutting concerns:

    From the quiz Component Store

Practise 650+ more Angular questions

Timed quizzes with instant feedback, the questions you miss coming back until they stick, coding challenges and AI mock interviews — free to start.