Observable (Subject-like) inputs for components and directives
See original GitHub issueWhich @angular/* package(s) are relevant/related to the feature request?
core
Description
There’re scenarios when a component’s or directive’s input change triggers some reactive pipeline. Moreover, some pipelines are triggered by a combination of inputs responding to change in any of them.
The simplest use case looks like this:
@Component({
selector: 'app-movie'
})
export class MovieComponent implements OnDestroy {
private readonly id$ = new ReplaySubject<string>(1);
@Input()
set id(value: string) {
this.id$.next(value);
}
readonly movie$ = this.id$.pipe(switchMap(id => this.collection.find(id)));
constructor(
private readonly collection: MovieCollection,
) {}
ngOnDestroy() {
this.id$.complete();
}
}
In order to get input as a stream of changes, we have to
- Add property containing some
Subject
, typicallyReplaySubject
for inputs without initial value orBehaviorSubject
for inputs with an initial value; - Use setter for input property and push
next
value to thatSubject
; - Don’t forget to clean up - implement
OnDestroy
andcomplete
theSubject
. - Repeat for every input of such kind.
Proposed solution
The solution to this problem is to allow using Subject
s as input values:
@Component({
selector: 'app-movie'
})
export class MovieComponent {
@Input()
readonly id = new ReplaySubject<number>(1);
readonly movie$ = this.id.pipe(switchMap(id => this.collection.find(id)));
constructor(
private readonly collection: MovieCollection,
) {}
}
Basically, any object implementing NextObserver
should be a legit value. Implementing CompletionObserver
should be optional.
Such inputs’ Subject
s must be completed automatically when the component/directive is destroyed.
Is may also require a special syntax for such sort of inputs:
<app-movie {id}="123"></app-movie>
Alternatives considered
An alternative may include the usage of a special implementation of Subject
, like EventEmitter
for outputs:
@Component({
selector: 'app-movie'
})
export class MovieComponent {
@Input()
readonly id = new InputSubject<number>(/* optional initial value */);
readonly movie$ = this.id.pipe(switchMap(id => this.collection.find(id)));
constructor(
private readonly collection: MovieCollection,
) {}
}
Issue Analytics
- State:
- Created a year ago
- Reactions:10
- Comments:10 (4 by maintainers)
Top GitHub Comments
@jessicajaniuk Do you think it will be addressed within 3 years? If not, to me that is equivalent to rejecting it
@pkozlowski-opensource Thanks for referring me to that comment. What wonders me is that the feature request was open in 2015th 🤯 and no changes since then. For such a long period of time, a lot of boilerplate code has been written 😅
Also, while reading @mgechev’s arguments about RxJS-independent core, I thought “What’s the point of doing this as each real-world app/lib inevitably uses streams? Streams are everywhere - in router, forms, HTTP client, in third-party libraries. It won’t have any impact on real-world apps bundle size”.
Streams are great - they’re declarative and composable. On the other hand, we have imperative lifecycle hooks like
OnChanges
, and a mixture of both makes code less readable and reliable.