Angular’s Output Symphony: Introducing the Output Function
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!