or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

application-management.mdchange-detection.mdcomponent-system.mddependency-injection.mdindex.mdmodern-authoring.mdresource-api.mdrxjs-interop.mdtesting.mdutilities-helpers.md
tile.json

modern-authoring.mddocs/

Modern Authoring APIs

Angular's modern authoring APIs provide signal-based alternatives to traditional decorators, offering better type safety, reactivity, and integration with Angular's signal system. These APIs represent the future direction of Angular development.

Capabilities

Signal-Based Inputs

Modern input declarations using signals for better reactivity and type safety.

/**
 * Creates an optional input signal
 * @returns InputSignal that may be undefined
 */
function input<T>(): InputSignal<T | undefined>;

/**
 * Creates an input signal with initial value
 * @param initialValue - Default value for the input
 * @returns InputSignal with the specified type
 */
function input<T>(initialValue: T): InputSignal<T>;

/**
 * Creates an input signal with options
 * @param options - Configuration options for the input
 * @returns InputSignal with specified configuration
 */
function input<T>(options: InputOptions<T>): InputSignal<T | undefined>;

/**
 * Creates an input signal with initial value and options
 * @param initialValue - Default value for the input
 * @param options - Configuration options for the input
 * @returns InputSignal with the specified type and configuration
 */
function input<T>(initialValue: T, options: InputOptions<T>): InputSignal<T>;

/**
 * Creates a required input signal
 * @returns InputSignal that must be provided
 */
input.required<T>(): InputSignal<T>;

/**
 * Creates a required input signal with options
 * @param options - Configuration options for the input
 * @returns InputSignal that must be provided
 */
input.required<T>(options: InputOptions<T>): InputSignal<T>;

/**
 * Signal representing an input property
 */
interface InputSignal<T> extends Signal<T> {
  readonly [InputSignalNode]: unknown;
}

/**
 * Configuration options for input signals
 */
interface InputOptions<T> {
  /** Alias for the input property */
  alias?: string;
  /** Transform function to convert input value */
  transform?: (value: any) => T;
}

Model Signals (Two-Way Binding)

Model signals enable two-way data binding with parent components using signal-based APIs.

/**
 * Creates an optional model signal for two-way binding
 * @returns ModelSignal that may be undefined
 */
function model<T>(): ModelSignal<T | undefined>;

/**
 * Creates a model signal with initial value
 * @param initialValue - Default value for the model
 * @returns ModelSignal with the specified type
 */
function model<T>(initialValue: T): ModelSignal<T>;

/**
 * Creates a model signal with options
 * @param options - Configuration options for the model
 * @returns ModelSignal with specified configuration
 */
function model<T>(options: ModelOptions): ModelSignal<T | undefined>;

/**
 * Creates a model signal with initial value and options
 * @param initialValue - Default value for the model
 * @param options - Configuration options for the model
 * @returns ModelSignal with the specified type and configuration
 */
function model<T>(initialValue: T, options: ModelOptions): ModelSignal<T>;

/**
 * Creates a required model signal
 * @returns ModelSignal that must be provided
 */
model.required<T>(): ModelSignal<T>;

/**
 * Creates a required model signal with options
 * @param options - Configuration options for the model
 * @returns ModelSignal that must be provided
 */
model.required<T>(options: ModelOptions): ModelSignal<T>;

/**
 * Signal representing a model property with two-way binding
 */
interface ModelSignal<T> extends WritableSignal<T> {
  readonly [ModelSignalNode]: unknown;
}

/**
 * Configuration options for model signals
 */
interface ModelOptions {
  /** Alias for the model property */
  alias?: string;
}

Output Signals

Modern output declarations using signals for event emission and communication with parent components.

/**
 * Creates an output signal for event emission
 * @returns OutputEmitterRef for emitting events
 */
function output<T>(): OutputEmitterRef<T>;

/**
 * Creates an output signal with options
 * @param options - Configuration options for the output
 * @returns OutputEmitterRef with specified configuration
 */
function output<T>(options: OutputOptions): OutputEmitterRef<T>;

/**
 * Reference to an output emitter for sending events to parent
 */
interface OutputEmitterRef<T> {
  /** Emit an event with the specified value */
  emit(value: T): void;
  
  /** Subscribe to output events (for advanced use cases) */
  subscribe(callback: (value: T) => void): OutputRefSubscription;
}

/**
 * Configuration options for output signals
 */
interface OutputOptions {
  /** Alias for the output property */
  alias?: string;
}

/**
 * Subscription to output events
 */
interface OutputRefSubscription {
  /** Unsubscribe from output events */
  unsubscribe(): void;
}

Query Functions

Modern query declarations using signals for accessing child elements and components.

/**
 * Query for a single view child element or component
 * @param locator - Selector or component type to query
 * @returns Signal containing the queried element or undefined
 */
function viewChild<T>(locator: ProviderToken<T>): Signal<T | undefined>;

/**
 * Query for a single view child with options
 * @param locator - Selector or component type to query
 * @param options - Query configuration options
 * @returns Signal containing the queried element or undefined
 */
function viewChild<T>(
  locator: ProviderToken<T>,
  options: ViewChildOptions
): Signal<T | undefined>;

/**
 * Query for multiple view child elements or components
 * @param locator - Selector or component type to query
 * @returns Signal containing array of queried elements
 */
function viewChildren<T>(locator: ProviderToken<T>): Signal<ReadonlyArray<T>>;

/**
 * Query for multiple view children with options
 * @param locator - Selector or component type to query  
 * @param options - Query configuration options
 * @returns Signal containing array of queried elements
 */
function viewChildren<T>(
  locator: ProviderToken<T>,
  options: ViewChildrenOptions
): Signal<ReadonlyArray<T>>;

/**
 * Query for a single content child element or component
 * @param locator - Selector or component type to query
 * @returns Signal containing the queried element or undefined
 */
function contentChild<T>(locator: ProviderToken<T>): Signal<T | undefined>;

/**
 * Query for a single content child with options
 * @param locator - Selector or component type to query
 * @param options - Query configuration options
 * @returns Signal containing the queried element or undefined
 */
function contentChild<T>(
  locator: ProviderToken<T>,
  options: ContentChildOptions
): Signal<T | undefined>;

/**
 * Query for multiple content child elements or components
 * @param locator - Selector or component type to query
 * @returns Signal containing array of queried elements
 */
function contentChildren<T>(locator: ProviderToken<T>): Signal<ReadonlyArray<T>>;

/**
 * Query for multiple content children with options
 * @param locator - Selector or component type to query
 * @param options - Query configuration options
 * @returns Signal containing array of queried elements
 */
function contentChildren<T>(
  locator: ProviderToken<T>,
  options: ContentChildrenOptions
): Signal<ReadonlyArray<T>>;

/**
 * Options for view child queries
 */
interface ViewChildOptions {
  /** Read a different token from the queried element */
  read?: any;
}

/**
 * Options for view children queries
 */
interface ViewChildrenOptions {
  /** Read a different token from the queried elements */
  read?: any;
}

/**
 * Options for content child queries
 */
interface ContentChildOptions {
  /** Read a different token from the queried element */
  read?: any;
  /** Whether to query descendants */
  descendants?: boolean;
}

/**
 * Options for content children queries
 */
interface ContentChildrenOptions {
  /** Read a different token from the queried elements */
  read?: any;
  /** Whether to query descendants */
  descendants?: boolean;
}

Signal Utility Functions

Core utility functions for working with signals and reactive programming.

/**
 * Creates a linked signal that derives its value from a source signal
 * @param source - Source signal to link from
 * @param computation - Function to compute the linked value
 * @returns WritableSignal linked to the source
 */
function linkedSignal<T, U>(source: Signal<T>, computation: LinkedSignalComputeFn<T, U>): WritableSignal<U>;

/**
 * Read signals without tracking dependencies in the current context
 * @param computation - Function to execute without tracking
 * @returns Result of the computation
 */
function untracked<T>(computation: () => T): T;

/**
 * Type guard to check if a value is a signal
 * @param value - Value to check
 * @returns True if value is a signal
 */
function isSignal(value: unknown): value is Signal<unknown>;

/**
 * Computation function for linked signals
 */
type LinkedSignalComputeFn<T, U> = (source: T, previous?: { value: U }) => U;

Attribute Transform Functions

Utility functions for transforming string attributes to typed values.

/**
 * Transform function for converting string attributes to boolean values
 * @param value - The attribute value to transform
 * @returns Boolean representation of the attribute
 */
function booleanAttribute(value: unknown): boolean;

/**
 * Transform function for converting string attributes to number values
 * @param value - The attribute value to transform
 * @returns Numeric representation of the attribute
 */
function numberAttribute(value: unknown): number;

Usage Examples

Signal-Based Component

import { Component, input, output, model, computed, effect } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    <div class="counter">
      <h3>{{title()}}</h3>
      <div>Count: {{displayValue()}}</div>
      <div>
        <button (click)="decrement()" [disabled]="isMinReached()">-</button>
        <button (click)="increment()" [disabled]="isMaxReached()">+</button>
      </div>
      <div *ngIf="showReset()">
        <button (click)="reset()">Reset</button>
      </div>
    </div>
  `,
  styles: [`
    .counter { 
      padding: 16px; 
      border: 1px solid #ccc; 
      border-radius: 4px; 
    }
    button { 
      margin: 0 4px; 
      padding: 8px 16px; 
    }
  `]
})
export class CounterComponent {
  // Inputs
  title = input('Counter');
  initialValue = input(0);
  min = input<number | undefined>(undefined);
  max = input<number | undefined>(undefined);
  step = input(1);

  // Model (two-way binding)
  value = model.required<number>();

  // Outputs
  valueChange = output<number>();
  minReached = output<void>();
  maxReached = output<void>();

  // Computed signals
  displayValue = computed(() => this.value() + this.initialValue());
  isMinReached = computed(() => {
    const minVal = this.min();
    return minVal !== undefined && this.value() <= minVal;
  });
  isMaxReached = computed(() => {
    const maxVal = this.max();
    return maxVal !== undefined && this.value() >= maxVal;
  });
  showReset = computed(() => this.value() !== this.initialValue());

  constructor() {
    // Effect to emit events when limits are reached
    effect(() => {
      if (this.isMinReached()) {
        this.minReached.emit();
      }
      if (this.isMaxReached()) {
        this.maxReached.emit();
      }
    });
  }

  increment(): void {
    if (!this.isMaxReached()) {
      const newValue = this.value() + this.step();
      this.value.set(newValue);
      this.valueChange.emit(newValue);
    }
  }

  decrement(): void {
    if (!this.isMinReached()) {
      const newValue = this.value() - this.step();
      this.value.set(newValue);
      this.valueChange.emit(newValue);
    }
  }

  reset(): void {
    this.value.set(this.initialValue());
    this.valueChange.emit(this.initialValue());
  }
}

Component with Queries

import { Component, viewChild, viewChildren, contentChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-form-container',
  template: `
    <form #mainForm>
      <ng-content></ng-content>
      <div class="form-actions">
        <button #submitBtn type="submit">Submit</button>
        <button #cancelBtn type="button">Cancel</button>
      </div>
    </form>
  `
})
export class FormContainerComponent implements AfterViewInit {
  // Query for form element
  mainForm = viewChild.required<ElementRef<HTMLFormElement>>('mainForm');
  
  // Query for buttons
  submitButton = viewChild<ElementRef<HTMLButtonElement>>('submitBtn');
  cancelButton = viewChild<ElementRef<HTMLButtonElement>>('cancelBtn');
  
  // Query for all input elements
  allInputs = viewChildren<ElementRef<HTMLInputElement>>('input');
  
  // Query for projected content
  formFields = contentChildren<ElementRef>('input, select, textarea');

  ngAfterViewInit(): void {
    // Access queried elements
    const form = this.mainForm().nativeElement;
    console.log('Form element:', form);

    // Subscribe to changes in query results
    effect(() => {
      const inputs = this.allInputs();
      console.log(`Found ${inputs.length} input elements`);
    });
  }
}

Input with Transform

import { Component, input, booleanAttribute, numberAttribute } from '@angular/core';

@Component({
  selector: 'app-config-panel',
  template: `
    <div [class.enabled]="enabled()">
      <h3>Configuration Panel</h3>
      <p>Max items: {{maxItems()}}</p>
      <p>Auto save: {{autoSave() ? 'On' : 'Off'}}</p>
    </div>
  `
})
export class ConfigPanelComponent {
  // Boolean attribute input with transform
  enabled = input(false, {
    transform: booleanAttribute
  });

  // Number attribute input with transform
  maxItems = input(10, {
    transform: numberAttribute
  });

  // Boolean input with alias and transform
  autoSave = input(true, {
    alias: 'auto-save',
    transform: booleanAttribute
  });
}

Two-Way Binding with Model

// Parent component
@Component({
  selector: 'app-parent',
  template: `
    <app-slider [(value)]="volume" [min]="0" [max]="100"></app-slider>
    <p>Current volume: {{volume}}</p>
  `
})
export class ParentComponent {
  volume = 50;
}

// Child component with model
@Component({
  selector: 'app-slider',
  template: `
    <div class="slider">
      <input 
        type="range" 
        [value]="value()" 
        [min]="min()" 
        [max]="max()"
        (input)="onSliderChange($event)"
      >
      <span>{{value()}}</span>
    </div>
  `
})
export class SliderComponent {
  // Two-way binding model
  value = model.required<number>();
  
  // Configuration inputs
  min = input(0);
  max = input(100);
  step = input(1);

  onSliderChange(event: Event): void {
    const target = event.target as HTMLInputElement;
    this.value.set(Number(target.value));
  }
}