DEV Community

Cover image for Angular new output() function
Davide Passafaro
Davide Passafaro

Posted on • Originally published at codemotion.com

Angular new output() function

During latest months, Angular v17 has introduced a set of new function-based APIs focused on enhance Signal integration within components and directives. This includes Signal Inputs, Model Inputs and Signal Queries.

Considering the full picture, we are only missing one core API to conclude this journey, and this article is dedicated to precisely that: Angular v17.3.0 latest addition, the new output() function.


The new output() API

Similar to the function-based APIs introduced so far, you now have a brand new output() function designed to replace the @Output decorator.

To declare an output, you can now use the output() function when we declare a property of a component or directive:

import { Component, output, OutputEmitterRef } from '@angular/core';

@Component({
  selector: 'my-component',
  standalone: true,
  template: `<button (click)="emitClick($event)">Click here</button>`,
})
export class MyComponent {

  buttonClick = output<MouseEvent>();

  alias = output<MouseEvent>({ alias: 'aliasClick' });

  emitClick(event: MouseEvent): void {
    this.buttonClick.emit(event);
    this.alias.emit(event);
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see from the example, this API also supports the alias property and exposes the emit() function.

Furthermore, you can listen for the output event in the parent components, using Angular event binding syntax in the template:

<my-component
  (buttonClick)="myFunction($event)"
  (aliasClick)="myFunction($event)"
/>
Enter fullscreen mode Exit fullscreen mode

So far, everything works almost identically to @Output decorator-based outputs.

@Output is replaced by the new ** raw `output()` endraw ** function

Let’s now move on to the subscribe() function, which brings some changes.

How to subscribe to outputs programmatically

Using the output() function you get an instance of type OutputEmitterRef:

buttonClick: OutputEmitterRef<MouseEvent> = output<MouseEvent>();
Enter fullscreen mode Exit fullscreen mode

In addition to the already mentioned emit() method, which has remained unchanged on the surface, this class also exposes a subscribe() method to listen to the event programmatically:

import { Component, Signal, effect, viewChild } from '@angular/core';
import { MyComponent } from './my-component';

@Component({
  selector: 'my-parent-component',
  standalone: true,
  imports: [MyComponent],
  template: `<my-component />`,
})
export class MyParentComponent {
  myComponentRef: Signal<MyComponent> = viewChild.required(MyComponent);

  constructor() {
    effect(() => {

      this.myComponentRef().myOutput.subscribe((event: MouseEvent) => {
        console.log('Manual subscription:', event);
      });

    });
  }
}
Enter fullscreen mode Exit fullscreen mode

The subscription generated by this class is not based on RxJs, so you can’t use the pipe() function and operators. Nevertheless, it still exposes an unsubscribe() function to terminate it programmatically:

const subscription = this.myComponentRef().myOutput.subscribe(
 (event: MouseEvent) => {
   // Unsubscribes to listen only the first event, if any
   subscription.unsubscribe();
  }
);
Enter fullscreen mode Exit fullscreen mode

It is also automatically completed when the component, or directive, is destroyed.

With new OutputEmitterRef you cannot use the ** raw `pipe()` endraw ** function, but it is automatically completed when the component is destroyed


New rxjs-interop functions

To further enhance the capabilities of this new API, two additional functions have been introduced in the RxJs Interop package.

outputFromObservable()

Thanks to the brand new outputFromObservable() function, you can now create an output starting from an Observable.

The generated output emits each new value emitted by the Observable:

import { Component, OutputRef } from '@angular/core';
import { outputFromObservable } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';

@Component({
  selector: 'my-timer-component',
  standalone: true,
  template: `...`,
})
export class MyTimerComponent {

 timer: OutputRef<number> = outputFromObservable(interval(1000));

 timerAlias = outputFromObservable(interval(1000), { alias: 'timerChange' });

}
Enter fullscreen mode Exit fullscreen mode

Both the Observable and the output are automatically completed when the component, or directive, is destroyed.

If an error occurs, the Observable is interrupted, consequently the output stops emitting and the error propagates (if not caught).

outputToObservable()

Thanks to the new outputToObservable() function we can instead transform an output into an Observable:

import { Component, Signal, effect, viewChild } from '@angular/core';
import { outputToObservable } from '@angular/core/rxjs-interop';
import { MyComponent } from './my-component';

@Component({
  selector: 'my-parent-component',
  standalone: true,
  imports: [MyComponent],
  template: `<my-component />`,
})
export class MyParentComponent {
  myComponentRef: Signal<MyComponent> = viewChild.required(MyComponent);

  constructor() {
    effect(() => {

      outputToObservable(this.myComponentRef().myOutput)
        .subscribe((event: MouseEvent) => {
          console.log('Manual subscription:', event);
        });

    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Again, both the Observable and the output are automatically completed when the component, or directive, is destroyed.

Furthermore, due to the absence of errors in the outputs, the resulting Observable never emits error notifications.


Thanks for reading so far 🙏

I’d like to have your feedback so please leave a comment, like or follow. 👏

Then, if you really liked it, share it among your community, tech bros and whoever you want. And don’t forget to follow me on LinkedIn. 👋😁

Top comments (3)

Collapse
 
jangelodev profile image
João Angelo

Hi Davide Passafaro,
Your tips are very useful.
Thanks for sharing.

Collapse
 
davidepassafaro profile image
Davide Passafaro

Thank you 😁

Collapse
 
fyodorio profile image
Fyodor

After they said they want to make Angular more accessible and appealing, something definitely went wrong 🤦‍♂️😄