What are Angular Signal Components?

    Mar 12, 20258 min read27 viewsUpdated:Mar 13, 2025
    What are Angular Signal Components?

    Introduction to Signal-Based Components

    Signal-based components are a new way of authoring angular components in Angular, providing a more efficient and effective way of handling data inputs and outputs. A signal based component simplifies data binding and change detection, offering advantages over traditional decorators like @Input() and @Output() by reducing verbosity and enhancing performance.

    What Are Signal-Based Components?

    Signal-based components use Angular Signals instead of RxJs for data inputs, outputs, and queries.

    They improve performance and change detection by allowing Angular to know which views depend on which signal. Signal based inputs replace traditional decorators, making the migration process smoother for developers. New signal inputs, currently in developer preview, further enhance code quality and developer experience.

    The main difference is that all decorators (@Input, @Output, @ViewChild, etc.) can now be replaced with functions.

    Benefits of Signal-Based Components:

    Enhanced Performance

    Signals enable more efficient change detection by tracking dependencies and updating only the necessary views.

    Simplified Data Handling

    Using functions instead of decorators streamlines the process of managing inputs and outputs, reducing boilerplate code.

    Reactive Programming

    Signals provide a powerful and intuitive way to handle reactive programming, making it easier to manage state changes and side effects.

    How to Use Signal-Based Components

    Inputs: Use writable signals to manage component inputs.

    const inputData = signal(initialValue); // Define an input signal

    Outputs: Use signals to manage component outputs.

    const outputData = signal(initialValue); // Define an output signal

    Computed Signals: Use computed signals to derive values based on other signals.

    const computedValue = computed(() => someCalculation(inputData()));

    Example:

    import { signal, computed } from 'angular-signals';
    
    @Component({
      selector: 'app-example',
      template: `
        <div>{{ computedValue() }}</div>
        <button (click)="increment()">Increment</button>
      `
    })
    export class ExampleComponent {
      inputData = signal(0); // Define a writable input signal
      computedValue = computed(() => this.inputData() * 2); // Define a computed signal
    
      increment() {
        this.inputData.update(value => value + 1); // Update the input signal
      }
    }

    Inputs default to undefined unless a default value is explicitly provided. Here, the default value for inputData is set to 0 to avoid having undefined inputs.

    Signal-based components represent a significant shift in how Angular developers can manage state and reactivity within their applications, offering a more streamlined and performant approach.

    Working with Signal Inputs

    Signal inputs allow us to create inputs that are signals, making them reactive. Input signals are treated as a read-only signal, allowing developers to monitor changes without altering the original signal's data. Here’s a detailed look at how to work with signal inputs in Angular:

    Key Features of Signal Inputs:

    Reactivity: Signal inputs are inherently reactive, automatically updating and propagating changes.

    Computed Signals: We can create computed signals from input signals and react to changes efficiently.

    Simplicity: Eliminates the need for input setters or the ngOnChanges lifecycle hook, making the code cleaner and more maintainable.

    Benefits of Signal Inputs

    Reactive Values: Signal inputs enable the use of reactive values and computed signals.

    Simplified API: The input() function simplifies the API for Angular components by directly replacing the @Input() decorator.

    Latest Values: The signal always holds the latest value of the input that is bound from the parent component.

    How to Use Signal Inputs

    Step-by-Step Guide:

    Define Signal Inputs: Use the input() function to create signal inputs.

    typescript
    import { input, computed } from 'angular-signals';
    @Component({
      selector: 'app-example',
      template: `
        <div>{{ computedValue() }}</div>
      `
    })
    export class ExampleComponent {
      inputData = input<number>(); // Define a signal input
      // Define a computed signal based on the input signal
      computedValue = computed(() => this.inputData() * 2);
    }

    Bind Input in Parent Component: Bind the signal input from the parent component.

    html
    <app-example [inputData]="parentValue"></app-example>

    React to Signal Changes: Use computed signals or effects to react to changes in signal inputs.

    typescript
    const count = input<number>();
    const doubleCount = computed(() => count() * 2);
    effect(() => {
      console.log(`Count changed to: ${count()}`);
    });
    Example:
    typescript
    import { Component, input, computed, effect } from 'angular-signals';
    @Component({
      selector: 'app-example',
      template: `
        <div>{{ fullName() }}</div>
      `
    })
    export class ExampleComponent {
      firstName = input<string>();
      lastName = input<string>();
      // Define a computed signal based on input signals
      fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
      constructor() {
        // React to changes in input signals
        effect(() => {
          console.log(`Full name changed to: ${this.fullName()}`);
        });
      }
    }

    With these steps, you can effectively work with signal inputs in Angular, leveraging their reactivity and simplicity to create more efficient and maintainable components.

    Signal Input Advanced Topics

    Initial Values:

    No Initial Value for Required Inputs: You cannot provide an initial value to a required input signal, as it must be provided by the parent component.

    Alias and Transform Functions:

    Alias Support: Signal inputs support alias properties, allowing you to specify alternative names for inputs, making the migration from decorator-based inputs smoother.

    typescript
    const aliasedInput = input<number>({alias: 'myAliasedInput'});

    Transform Functions: Use the transform function to manipulate input values before they are available in the component scope. This allows for preprocessing and validation of input data.

    typescript
    const transformedInput = input<number>({transform: (value) => value * 2});

    Example:

    typescript
    import { Component, input, computed, effect } from 'angular-signals';
    @Component({
      selector: 'app-advanced-example',
      template: `
        <div>{{ transformedValue() }}</div>
      `
    })
    export class AdvancedExampleComponent {
      // Optional input
      optionalInput = input<string>();
      // Required input
      requiredInput = input<number>().required();
      // Aliased and transformed input
      aliasedTransformedInput = input<number>({
        alias: 'aliasedInput',
        transform: (value) => value * 2
      });
      // Computed signal based on the transformed input
      transformedValue = computed(() => this.aliasedTransformedInput());
      constructor() {
        // React to changes in the required input
        effect(() => {
          console.log(`Required input changed to: ${this.requiredInput()}`);
        });
      }
    }

    By leveraging these advanced features, you can create more dynamic and flexible components, ensuring that input properties are properly managed and validated.

    Transforming and Aliasing Signal Values

    Transforming and aliasing signal values are essential features in Angular Signal Components that enhance the flexibility and readability of your code.

    Two-Way Data Binding with Model()

    Two-way data binding is a powerful feature in Angular Signal Components that allows for seamless synchronization between the component and the view. The model() function enables you to create a writable signal that can be used for two-way data binding.

    To use the model() function, you can pass an initial value as an argument. This value can be a plain value or a signal.
    Here’s an example:

    import { Component, model } from 'angular-signals';
    
    @Component({ 
      selector: 'app-example', 
      template: '<input [(ngModel)]="myModel()">' 
    }) 
    export class ExampleComponent { 
      myModel = model<string>('initial value'); 
    }

    In this example:

    • The myModel signal is created with an initial value of "initial value".

    • The [(ngModel)] directive binds the input element to the myModel signal.

    • When the user types in the input field, the myModel signal is updated dynamically, and the input reflects the new value.

    Using writable signals for two-way data binding simplifies the process of keeping the component state and the view in sync, making your code more maintainable and reactive.

    Signal Outputs and Queries

    Signal-based components introduce a new way to handle component outputs and queries in Angular, making the process more efficient and reactive.

    Signal Outputs:

    output() API: The output() API is a direct replacement for the traditional @Output() decorator.

    OutputEmitterRef: It returns an OutputEmitterRef and allows you to define a component output in Angular.

    Defining Outputs: You can use the output() API to define a component output.

    typescript
    import { Component, output, OutputEmitterRef } from 'angular-signals';
    @Component({
      selector: 'app-output-example',
      template: `
        <button (click)="emitEvent()">Emit Event</button>
      `
    })
    export class OutputExampleComponent {
      eventOutput: OutputEmitterRef<string> = output<string>();
      emitEvent() {
        this.eventOutput.emit('Event Data'); // Emit event data
      }
    }

    Signal-Based Queries:

    View Queries as Signals: Signal-based queries provide the results of view queries as signals instead of normal view queries.

    Simplified Lifecycle Management: This approach eliminates the need for lifecycle hooks like ngAfterContentInit, ngAfterViewChecked, and ngAfterContentChecked.

    Example:
    typescript
    import { Component, ViewChild, ElementRef, signal, Signal } from 'angular-signals';
    @Component({
      selector: 'app-query-example',
      template: `
        <div #contentDiv>Content</div>
      `
    })
    export class QueryExampleComponent {
      @ViewChild('contentDiv') contentDiv!: ElementRef;
      contentDivSignal: Signal<ElementRef> = signal(this.contentDiv); // View query as a signal
      constructor() {
        // React to changes in the query signal
        effect(() => {
          console.log('Content Div:', this.contentDivSignal());
        });
      }
    }

    Benefits of Signal Outputs and Queries:

    Reactive Data Handling

    Both outputs and queries are handled reactively, ensuring seamless updates and interactions.

    Reduced Boilerplate

    Signals reduce the need for boilerplate code, making components cleaner and more maintainable.

    Efficient Change Detection

    Signals enhance performance by allowing Angular to track and update dependencies accurately.

    Signal Outputs and Interoperability

    Signal outputs are a key feature in Angular Signal Components that allow you to emit values to the parent component, facilitating communication between components.

    To create an output signal, you can use the output() function.

    import { Component, output } from 'angular-signals';
    
    @Component({ 
      selector: 'app-example', 
      template: '<button (click)="emitValue()">Emit value</button>' 
    }) 
    export class ExampleComponent { 
      myOutput = output<string>();
    
      emitValue() { 
        this.myOutput.emit('new value'); 
      } 
    }

    In this example:

    • The myOutput signal is created.

    • The emitValue() function is used to emit a new value to the parent component.

    import { Component, outputFromObservable } from 'angular-signals';
    import { of } from 'rxjs';
    
    @Component({ 
      selector: 'app-example', 
      template: '<button (click)="emitValue()">Emit value</button>' 
    }) 
    export class ExampleComponent { 
      myOutput = outputFromObservable(of('initial value'));
    
      emitValue() { 
        this.myOutput.emit('new value'); 
      } 
    }

    In this example, the myOutput signal is created from an observable using the outputFromObservable() function, demonstrating the interoperability between signal components and RxJS.

    Using Signal Outputs with RxJS Observables

    Signal outputs can also be used with RxJS observables and subjects, providing flexibility in handling asynchronous data streams. The outputFromObservable() function allows you to create an output signal from an observable.

    Fine-grained Change Detection with Input Signal

    Angular's current change detection mechanism relies on Zone.js, which patches browser APIs to notify Angular of changes. However, with the introduction of signals, Angular is moving towards a more efficient and fine-grained change detection system.

    Key Points:

    Zone.js: Angular uses Zone.jsto monitor and trigger change detection. This involves patching browser APIs to keep track of async operations and detect changes.

    Fine-Grained Change Detection: Signals enable Angular to achieve fine-grained change detection by knowing exactly which views are affected by a signal change.

    Targeted Updates: Instead of checking the entire component tree, Angular can update only the specific parts of the view that depend on the changed signals.

    Elimination of Dirty Checking: Signals eliminate the need for dirty checking, reducing the performance overhead associated with it.

    Benefits:

    Improved Performance: By updating only the necessary views, signals improve the overall performance of the application.

    Simplified Change Detection: No need for Zone.js, making the framework lighter and more straightforward.

    Reduced Reliance on RxJs: Signal-based components reduce the reliance on RxJs, making them easier to learn and use.

    Example:

    typescript
    import { Component, signal, computed, effect } from 'angular-signals';
    @Component({
      selector: 'app-fine-grained-change-detection',
      template: `
        <div>{{ computedValue() }}</div>
        <button (click)="increment()">Increment</button>
      `
    })
    export class FineGrainedChangeDetectionComponent {
      count = signal(0); // Define a signal
      computedValue = computed(() => this.count() * 2); // Define a computed signal
      increment() {
        this.count.update(value => value + 1); // Update the signal
      }
      constructor() {
        // Effect to log changes in the signal
        effect(() => {
          console.log(`Count changed to: ${this.count()}`);
        });
      }
    }

    Signal Value Transformation and Best Practices

    Transforming Input Values:

    Input Transform Function: You can transform the input value before it is assigned to the input signal using an input transform function. This allows you to manipulate or validate input values before they are used within the component.

    Pure Functions: The transform function should be a pure function, meaning it has no side effects and always produces the same output given the same input.

    Example:

    typescript
    const transformedInput = input<number>({
      transform: (value) => value * 2 // Transformation logic
    });

    Deriving Values from Signal Inputs:

    Computed API: Deriving values from signal inputs is possible using the computed() API. Computed signals are recalculated whenever the input signal value changes.

    Reactivity: This ensures that derived values are always up-to-date with the latest input values.

    Example:

    typescript
    const inputValue = input<number>();
    const derivedValue = computed(() => inputValue() * 2);

    Interoperability with RxJs:

    Seamless Integration: Signals are designed to be interoperable with RxJs, allowing you to continue using RxJs for more complex operator chains and async operations.

    Flexibility: You can choose to use either signals or RxJs based on the complexity and requirements of your application.

    Best Practices:

    Use Pure Functions: Ensure that transform functions are pure, with no side effects, to maintain predictable behavior.

    Leverage Computed Signals: Utilize computed signals to derive values and ensure reactivity.

    Combine with RxJs When Needed: Use RxJs for complex scenarios that require advanced operators, while using signals for simpler reactive data handling.

    Avoid Unnecessary Dependencies: Minimize dependencies on lifecycle hooks and external libraries by leveraging the built-in capabilities of signals.

    Consistent Naming Conventions: Follow consistent naming conventions for signals, computed signals, and outputs to improve code readability and maintainability.

    Example of Signal Value Transformation and Computed Signals:

    typescript
    import { Component, input, computed } from 'angular-signals';
    @Component({
      selector: 'app-transform-example',
      template: `
        <div>Transformed Value: {{ transformedValue() }}</div>
        <div>Derived Value: {{ derivedValue() }}</div>
      `
    })
    export class TransformExampleComponent {
      // Input signal with a transform function
      inputValue = input<number>({
        transform: (value) => value * 2
      });
      // Computed signal derived from the input signal
      derivedValue = computed(() => this.inputValue() + 10);
      constructor() {
        // Log the transformed and derived values
        effect(() => {
          console.log(`Transformed Value: ${this.inputValue()}`);
          console.log(`Derived Value: ${this.derivedValue()}`);
        });
      }
    }

    Migration and Optimization Strategies

    Migration to Signal Inputs:

    Production-Ready: Signal inputs have graduated to production-ready status, offering a robust alternative to decorator-based inputs.

    New Migrations: The Angular team provides new migration tools to update @Input declarations to the new signal API.

    Tooling Updates: Migration tooling updates @Input declarations and adjusts references to inputs, introducing variables where necessary.

    Configurable Options: The migration process is configurable, allowing you to choose the right strategy for your codebase. You can migrate all at once or on specific paths.

    Steps for Migration:

    Run the Migration Tool: Use Angular's CLI to initiate the migration process, ensuring that your project is backed up before proceeding. The tool will automatically update your codebase to replace @Input decorators with the new input() function, streamlining signal inputs across your components.

    Specify Paths: Use the --path option to limit the migration to specific sub-directories, allowing for a phased migration approach.

    Best-Effort Mode: Enable eager migration with the --best-effort-mode flag to automatically attempt to migrate as much code as possible in one go.

    Insert TODOs: Add TODOs for inputs that couldn't be migrated with the --insert-todos option, providing a list of items that require manual review or adjustment.

    Optimization Strategies:

    Eliminate Zone.js: Signals provide a fine-grained change detection mechanism, eliminating the need for Zone.js and improving application performance.

    Reduce RxJs Reliance: Signal-based components reduce reliance on RxJs, making them easier to learn and use. However, you can still use RxJs for complex scenarios requiring advanced reactive patterns.

    Leverage Computed Signals: Use computed signals to derive values from input signals, ensuring reactivity and up-to-date data, thus enhancing performance and simplifying code.

    Example of Migrated Code:

    typescript
    import { Component, input, computed } from 'angular-signals';
    @Component({
      selector: 'app-migrated-example',
      template: `
        <div>{{ computedValue()

    Conclusion and Best Practices

    Angular Signal Components offer a powerful and efficient way to create reactive and maintainable components. By leveraging signal inputs, outputs, and two-way data binding, you can build complex and interactive components with ease.

    24

    Related articles

    This website uses cookies to analyze website traffic and optimize your website experience. By continuing, you agree to our use of cookies as described in our Privacy Policy.