Angular’s Output Symphony: Introducing the Output Function

Netanel Basal
Netanel Basal
Published in
3 min readMar 8, 2024

--

Angular v17.3 marks a significant milestone with the introduction of a symmetrical API complementing input() and model()—the output function. This function mirrors the capabilities of the Output decorator. Declaring a property as output is as simple as invoking the output() function:

import { output } from '@angular/core';

@Component({
selector: 'app-foo',
template: '',
standalone: true,
})
class FooComponent {
page = output<number>();
// Or use an alias
page = output<number>({ alias: 'currentPage' });
}

Subsequently, we subscribe to it within the template:

import { output } from '@angular/core';

@Component({
selector: 'app-main',
template: '<app-foo (page)="doSomething()">',
standalone: true,
})
class AppComponent {
doSomething() {}
}

Diverging from the EventEmitter, which extends RxJS Subject class, the output function returns an OutputEmitterRef, with only two exposed methods — subscribe and emit. As I highlighted in my 2017 blog post, this flexibility allows us to substitute EventEmitter with any other observable within our Output decorator.

Creating Output from Observable

To maintain compatibility with existing workflows, Angular introduces outputFromObservable. This utility function enables the creation of outputs from observables, facilitating a smooth transition for developers:

import { outputFromObservable } from '@angular/core';

@Component({
selector: 'app-foo',
template: '',
standalone: true,
})
class FooComponent {
form = new FormGroup({ ... })
value = outputFromObservable(this.form.valueChanges);
// You can also use an alias
value = outputFromObservable(this.form.valueChanges, { alias: 'valueChange' });
}

It can take various data sources such as Subjects and EventEmitters. Let’s delve into its implementation:

export function outputFromObservable<T>(
observable: Observable<T>, opts?: OutputOptions): OutputRef<T> {
return new OutputFromObservableRef<T>(observable);
}
class OutputFromObservableRef<T> implements OutputRef<T> {
private destroyed = false;

destroyRef = inject(DestroyRef);

constructor(private source: Observable<T>) {}

subscribe(callbackFn: (value: T) => void): OutputRefSubscription {
const subscription = this.source.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe({
next: value => callbackFn(value),
});

return {
unsubscribe: () => subscription.unsubscribe(),
};
}
}

The OutputFromObservableRef class accepts an observable and implements the OutputRef subscribe method. When invoked, it subscribes to the provided observable and passes emitted values to the callback function. Moreover, it ensures cleanup when the associated component or directive is destroyed.

Converting Output to Observable

Additionally, we have the option to utilize outputToObservable, which converts an output into an observable:

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

@Component({
selector: 'app-foo',
template: '',
standalone: true,
})
class FooComponent {
page = output<number>();
page$ = outputToObservable(this.page);
}

The implementation is straightforward:

function outputToObservable<T>(ref: OutputRef<T>): Observable<T> {
const destroyRef = ɵgetOutputDestroyRef(ref);

return new Observable<T>(observer => {
destroyRef?.onDestroy(() => observer.complete());
const subscription = ref.subscribe(v => observer.next(v));
return () => subscription.unsubscribe();
});
}

This function returns a new observable that subscribes to the provided ref and forwards its values to the observer. It’s noteworthy that while error handling is not included, the stream is completed upon destruction.

🙏 Support ngneat & Netanel Basal: Get Featured!

Are you passionate about the ngneat open source libraries for Angular or find Netanel Basal’s blog posts invaluable for your learning journey? Show your support by sponsoring my work!

Follow me on Medium or Twitter to read more about Angular and JS!

--

--

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.