Asymmetry of Angular Inputs and Outputs

Your output is not a state

Matthieu Riegler -

In Angular, inputs and outputs are mechanisms used to communicate between components. An input() allows a parent component to pass data into a child component, while an output() enables the child to pass back to the parent.

input() and output() are often seen as symetrical APIs. One example of that is output() is often wrong call a signal output.

We'll see now, why they're are actually not symetrical APIs and what it implies.

Inputs, a state based communication.

Inputs represent a state-based communication, meaning they allow a parent component to control the state of a child component by passing data down through properties.

Conveniently, inputs are represented with signals. A reactive state based API.

@Component()
class MyButton {
  disabled = input<boolean>(false);
}

In this example, the disabled input can be read at anytime (it is not required and has a initial value). The signal based reactivity allows the component to define specific behaviors when the value changes.

Ouput, an event based communication

Outputs represent an event-based communication chanel, allowing a child component to notify its parent when something has happened. They are usually triggered by another event.

@Component({
  template: `
    <button (click)="sendMessage()">Click Me</button>
  `,
})
export class ChildComponent {
  messageEvent = output<string>();

  sendMessage() {
    this.messageEvent.emit('Hello from Child!');
  }
}

As you can see, there is no real symmetry between inputs and ouputs.

Inputs/Ouputs in the era of signals.

If you're up-to date with your pattern you'll know that signals promote state based programming with declarative derivation (computed, linkedSignal,resource etc). Often they are compared to an other reactive primitive Observable (see RxJs), which rely on events.

This why we end up with a signal when defining an input(), but output() does't exhibit no rely on signals. Outputs being event based, they are a perfect canditate for an rxjs-interop help : outputFromObservable.

@Directive({..})
export class MyDir {
  nameChange$ = <some-observable>;
  nameChange = outputFromObservable(this.nameChange$);
}

With outputFromObservable get a 1-to-1 behavior, each time the observable emits, a new value is emitted to the parent.

State changes as an output ?

With signal being used in more and more codebases, we seen some interesting feature request spawning. Like for example an outputFromSignal helper.

This is an interesting one, as it would create new interface between a state based world and an event-based world. Basically such helper could behave as outputFromObservable(toObservable(mySignal()).


Suggestions