Angular new output() function
Tags: angular, frontend, webdev, javascript
Published: 2024-04-24 07:00:00

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](https://dev.to/davidepassafaro/angular-signal-inputs-road-to-signal-components-32cl)**, **[Model Inputs](https://dev.to/davidepassafaro/angular-model-inputs-two-way-binding-inputs-with-signals-2nc7)** and **[Signal Queries](https://dev.to/davidepassafaro/angular-signal-queries-simplifying-dom-querying-hmh)**.

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:

```ts
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);
  }
}
```

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:

```html
<my-component
  (buttonClick)="myFunction($event)"
  (aliasClick)="myFunction($event)"
/>
```

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

![@Output is replaced by the new **`output()`** function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dqka7widqqcnxdhp2ovk.jpeg)

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**:

```ts
buttonClick: OutputEmitterRef<MouseEvent> = output<MouseEvent>();
```

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:

```ts
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);
      });

    });
  }
}
```

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:

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

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


![With new OutputEmitterRef you cannot use the **`pipe()`** function, but it is automatically completed when the component is destroyed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/flrl603ey9vwg9nael4l.jpeg)

---

## 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**:

```ts
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' });
 
}
```

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**:

```ts
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);
        });

    });
  }
}
```

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](https://www.linkedin.com/in/davide-passafaro/)**. 👋😁