../

Angular cheatsheet

January 19, 2026

Typescript ·Angular

This post is a amalgamation of knowledge and other resources, to help me remember useful tools/tricks to build complex Angular web-applications.

Note that as of writing, the latest version of Angular v21.


Templating

  • When checking if a signal exists, you can assign the value of the signal to a new variable, within the if statement. (documentation)

    @if (signalValue(); as value) {
      <p>{{ value }}</p>
    }

  • When using a for-loop in a template, you can use @empty to provide markup for when there are no items in the list. (documentation)

    @for (let item of items(); track $index) {
      <p>{{ item }}</p>
    } @empty {
      <p>no items!</p>
    }

  • [routerOutletData] allows for passing of data to sub-routes

    // app.component.ts
    export interface OutletData {
      username: string;
    }
    
    @Component({
      template: `@if (outletData(); as data) {
          <router-outlet [routerOutletData]="data" />
        } @else {
          <p>Loading...</p>
        }`
    })
    export class AppComponent {
      outletData = signal<OutletData | null>(null);
    }

    Then, the sub-route could look like this:

    // home.component.ts
    import { type OutletData } from 'app.component.ts';
    
    @Component({
      template: `<p>Hi, {{ outlet().username }}!</p>`
    })
    export class ProjectPage {
      outlet = inject(ROUTER_OUTLET_DATA) as Signal<OutletData>;
    }

  • When listening for key events, you can use (keydown.<key>) to only listen to that specific key. (documentation)

    <input type="text" (keyup.enter)="submit()" />
    
    <!-- Optional modifiers (matches shift + enter) -->
    <input type="text" (keyup.shift.enter)="submit()" />

Event manager plugins

Event manager plugins are a way to create custom event handlers, like this one to prevent default events from executing:

import { Injectable } from '@angular/core';
import { EventManagerPlugin } from '@angular/platform-browser';

@Injectable()
export class PreventDefaultEventPlugin extends EventManagerPlugin {
  constructor() {
    super(document);
  }

  // Define which events this plugin supports
  override supports(eventName: string) {
    return /preventDefault/.test(eventName);
  }

  // Handle the event registration
  override addEventListener(
	element: HTMLElement,
	eventName: string,
	handler: Function
  ) {
    // Parse the event (e.g. 'submit.preventDefault')
    const [event] = eventName.split('.');

    const listener = (event: Event) => {
      // Actually prevent the default event
      event.preventDefault();
      handler(event);
    };

    element.addEventListener(event, listener);

    // Return cleanup function
    return () => element.removeEventListener(event, listener);
  }
}

Then, don’t forget to provide it in the providers array in the bootstrapApplication function


Directives

Directives can be used for all sorts of things, but in this example I use it to create a tab component:

@Directive({ selector: '[appTabListItem]' })
export class TabListItemDirective {
  public readonly templateRef = inject(TemplateRef<unknown>);
}

@Directive({ selector: '[appTabContent]' })
export class TabContentDirective {
  public readonly templateRef = inject(TemplateRef<unknown>);
}

@Directive({ selector: '[appTabsLayout]' })
export class TabsLayoutDirective {
  public readonly templateRef = inject(
    // By adding `list` and `content` here, we can
    // access them by using `let-list` in templates
    TemplateRef<{
      list: TemplateRef<unknown>;
      content: TemplateRef<unknown>;
    }>
  );
}

@Component({
  template: `<ng-container
    *ngTemplateOutlet="
      layout()!.templateRef;
      context: {
        list: listTemplate,
        content: contentTemplate,
      }
    "
  />`
})
export class TabsComponent {
  layout = contentChild(TabsLayoutDirective);
  listItems = contentChildren(TabListItemDirective);
  contents = contentChildren(TabContentDirective);
  activeIndex = signal(0);
}

Then to use it:

<app-tabs>
  <!-- Define a template for how to align the list and content -->
  <ng-template appTabsLayout let-list="list" let-content="content">
    <div class="grid size-full grid-cols-4">
      <div
        class="col-span-1 flex flex-col gap-1 border-r border-neutral-200 p-2 transition-colors dark:border-neutral-700"
      >
        <ng-container *ngTemplateOutlet="list" />
      </div>

      <div class="col-span-3 p-4">
        <ng-container *ngTemplateOutlet="content" />
      </div>
    </div>
  </ng-template>

  <!-- Then we can add as many tabs as we want -->
  <ng-template appTabListItem>
    <p>Tab 1</p>
  </ng-template>

  <ng-template appTabContent>
    <p>Some content here</p>
  </ng-template>

  <ng-template appTabListItem>
    <p>Tab 2</p>
  </ng-template>

  <ng-template appTabContent>
    <p>Some other content here</p>
  </ng-template>
</app-tabs>

Random snippets

  • Boolean input transformer

    /**
     * @example
     * import { transform } from '@core/utils/merge-classes';
     *
     * class ExampleComponent {
     *  option = input(false, { transform })
     * }
     *
     * <example-component option />
     *
     * @param value
     * @returns boolean
     */
    export function transform(value: boolean | string): boolean {
      return typeof value === 'string' ? value === '' : value;
    }

  • Merge tailwind classes

    import clsx, { ClassValue } from 'clsx';
    import { twMerge } from 'tailwind-merge';
    
    export function mergeClasses(...inputs: ClassValue[]) {
      return twMerge(clsx(inputs));
    }

  • Component with selector that matches an input field

    @Component({
      // note this selector is the same as the input below
      selector: '[appTooltip]',
      template: '<ng-content />', // show the original content
      host: {
        '[title]': 'tooltipContent()',
      },
    })
    export class TooltipComponent {
      appTooltip = input<string>('');
    
      // you can also use an alias to make this more readable:
      tooltipContent = input<string>('', {
        alias: 'appTooltip',
      });
    }

    Then you can use it like this:

    @Component({
      template: `<p appTooltip="tooltip content">Hover over me to see the tooltip</p>`,
      imports: [TooltipComponent] // importing is important
    })
    export class ExampleComponent {}

Useful Angular


Links

Some libraries and useful links:

Copyright © 2016-2026 boris.foo, All rights reserved.