You're most of the way there with your pseudocode. All you need is an initial value for the search key (I used null, with this implementation an empty string would work too). Then you can choose what to switch into.
data$: Observable<someData[]>();
searchKeyChanged: Subject<string>();
ngOnInit() {
this.data$ = searchKeyChanged.pipe(
startWith(null),
switchMap(searchKey =>
searchKey != null && searchKey?.length > 0 ?
this.dataService.getDataBySearchKey(searchKey) :
this.dataService.getAllData()
)
);
}
This is also the ideal use case for a switchMap as in-flight service calls will get canceled partway through if the user keeps typing and new searchKeys come in.
Aside: debounce is nice
If the user can change the searchKeys in rapid succession, I would recommend a debounce to smooth out the user experience.
ngOnInit() {
this.data$ = searchKeyChanged.pipe(
debounceTime(500),
startWith(null),
switchMap(searchKey =>
searchKey != null && searchKey?.length > 0 ?
this.dataService.getDataBySearchKey(searchKey) :
this.dataService.getAllData()
)
);
}
This will now make a new service call if the user stops searchKeyChanged firing for half a second. It means that data$ won't stutter so much. It's also the usual rhythm for a user to stop a moment and then expect results.
If your service calls are slow to complete but can be canceled party-way (They're not converted promises, for example), then I would debounce AFTER the service call. This is more computation heavy but the results should appear 500ms faster than if you debounce before.
ngOnInit() {
this.data$ = searchKeyChanged.pipe(
startWith(null),
switchMap(searchKey =>
searchKey != null && searchKey?.length > 0 ?
this.dataService.getDataBySearchKey(searchKey) :
this.dataService.getAllData()
),
debounceTime(500)
);
}
Of course, you can change the time or use one of RxJS's other debounce operators