Interactive Example

What follows is a peek behind the curtain at what the Angular Team hasn't officially documented yet...

⚠️ a word of caution:

import { ɵinput as input } from '@angular/core';
Enter fullscreen mode Exit fullscreen mode

don't try this in production!

The StackBlitz project below lets you explore Signal Input declarations firsthand. Right now, Angular (v17.1.0-next.5) doesn't include the actual Signal Input runtime, so we're limited to examining the type declarations and the public surface they expose.

Table of Contents

🛠️ first - code examples
🥩 then - the underlying types

We'll walk through each example from the StackBlitz above in detail, and after that, we'll dissect the internal type machinery that powers them.

Illustrative Scenarios

Let's kick things off with a series of examples. Here's the first one:

🔍 Similar to standard signals, the initial value determines the input's type:

signal usage screen

⚠️ It's crucial to remember that Input Signals are READONLY. This is logical because it's the parent component that provides new values through the template, not the component itself 😉

signal usage screen

A design choice I particularly admire:
💪 It leverages (1) the capabilities of #typescript and (2) adopts effective patterns from other frameworks, notably React.

💜🩷💜 Absolutely love it 🩷💜🩷

signal usage screen

While less thrilling - and IMHO it's best minimized since it can hinder code readability and obscure component data flow - aliasing is supported:

📡 The parent component uses the aliased name to pass data
🔐 Internally, the property name is used (naturally)

signal usage screen

Another well-thought-out decision:
💪 Since an input is REQUIRED, providing an initial value would be illogical 😉, so it's excluded from the function's signature.

signal usage screen

In this scenario, the initialValue parameter is omitted from the function's overload signature. More accurately, it's never permitted to be present there 😉.

Consequently, TypeScript would attempt to pass your initial value as an options object, which leads to a type error.

signal internals code

One more illustration - the transform option is only permissible when both ReadT and WriteT type parameters are supplied. We'll delve deeper into this shortly...

signal usage screen

Under the Hood

Time to examine 🍖 the internal workings 🥩

signal internals code

For a high-level view, I recommend checking out the ➡️ Angular Team's Sub-RFC 3: Signal-based Components, which covers the strategy for using signals in components. You can also ➡️ examine the source code directly. Anyway, let's get into the details!

We encounter two new signal-related symbols:

signal internals code

This follows the same pattern as the original unique Signal symbol, which was designed to limit compatibility between various (input) signals.

For instance:
✅ You can assign an InputSignal<number, number> to a standard Signal<number> variable.

signal internals code

Here's the demonstration:

  1. We first need to make the standard signal READONLY so that TypeScript can compare two readonly signals (input signals are READONLY).

  2. Where a string signal is needed, a string input signal is valid because it possesses all the necessary properties.

  3. ❌ The reverse fails, as a standard string signal lacks the branded read/write properties.

signal compatibility proof code

Reassigning signals is unlikely to be a common operation 😉. However, the Angular compiler leverages these symbols in its own processes.

Now, this is the source of the signals API. There are distinct implementations for optional and required inputs. The public API is simply a convenient wrapper:

s1 = input()
s2 = input.required()
Enter fullscreen mode Exit fullscreen mode

signal internals code

Unfortunately, we aren't able to observe its runtime behavior at this moment.

(Important note: as stated, ng v17.1 is imminent. This analysis is based on v17.0.1-next.5).

signal internals code

Let's revisit the concept of input transforms:

signal usage code

You might be curious about the purpose of the function overload and the peculiar declaration at the bottom:

Diving into Type System behind Angular Signal Inputs — figure 15

Firstly, if you provide only <ReadT>, the input transform is not available (similar to the current behavior with.

@Input
({ transform: ... })
Enter fullscreen mode Exit fullscreen mode

signal internals code

Conversely, by passing both <ReadT, WriteT>, you gain the ability to include a transform. Keep in mind that:

  • ReadT defines the type you use within your component.
  • WriteT defines the type that originates from external sources.

signal internals code

Here's another instance:

signal usage code

Returning to our intricate declaration 🥴, let's first concentrate on inputFunction:

signal internals code

It features triple overloads:

  • When no initial value is provided, the inner type is extended with undefined.
  • When an initial value is provided with only the ReadT type parameter (opts WITHOUT transform).
  • When an initial value is provided with both ReadT, WriteT type parameters (opts WITH transform).

This covers all the scenarios we've explored 😎💪

signal internals code

In summary, there are 3 overloads for the optional prop = input()
... and 2 overloads for the required prop = input.required().

signal internals code

That was quite a journey 😅 Hope you found it insightful.

Concluding Thoughts

We've examined numerous examples of how using input signals in different ways affects their underlying TypeScript types. The best approach depends on your unique needs for a given situation. Nevertheless, it's vital to always be mindful of the types that are declared or inferred, as type safety is a cornerstone of overall code quality.

While the Signal Input types are meticulously defined, the responsibility lies with you to determine whether an input should be optional or required in the long term. Types should mirror your design intentions.

Feel free to experiment with it on this stackblitz. It contains all the examples discussed.

Follow me on twitter for more in-depth explorations of frontend topics (js, ts, react, angular, etc.)!