One of the most important aspects of software architecture might be the concept of decoupling pieces of code. Therefore we could consider passing streams to child components as a bad practice because it creates a very tight link between the parent component and the child component. They are no longer decoupled since subscriptions in the child component might trigger actions in the parent component. We never want the child component to be responsible of initiating data calls right?! That’s the task of the smart component. See the difference between smart and dumb components here. A component should always receive an object or value and should not even care if that object or value comes from a stream or not.
// BAD
// app.component.ts
@Component({
selector: 'app',
template: `
<!--
BAD: The users$ steram is passed
to user-detail directly as a stream
-->
<user-detail [user$]="user$"></user-detail>
`
})
class AppComponent {
// this http call will get called when the
// user-detail component subscribes to users$
// We don't want that
users$ = this.http.get(...);
...
}
// user-detail.component.ts
@Component({
selector: 'user-detail',
template: `
`
})
class UserDetailComponent implements OnInit {
@Input() user$: Observable<User>;
user: User;
ngOnInit(){
// WHOOPS! This child component subscribes to the stream
// of the parent component which will do an automatic XHR call
// because Angular HTTP returns a cold stream
this.user$.subscribe(u => this.user = u);
}
}
Even if you implement take(1) still you may need to pass an unsubscription function. Below example
downloadFunction() {
let taskId: number;
this.downloadSubscription = this.taskId$
.pipe(
take(1),
mergeMap(taskId => {
taskId = taskId;
return this.bulkImportWizardService.downloadTemplateWithIssues(
taskId
);
})
)
.subscribe(blob => {
fileSaver.saveAs(blob, `template_with_issues_${taskId}.xlsx`);
});
this.trackSubscription(this.downloadSubscription);
}
And in above trackSubscription() is a separate function coming from a mixin that will manage subscription and unsubscription (onNgOnDestroy)
Reason to still unsubscribe() even with take(1)
Some subscriptions only have to happen once during the application startup. They might be needed to kick-start some processing or fire the first request to load the initial data. In such scenarios we can use RxJS take(1) operator which is great because it automatically unsubscribes after the first execution.
But note that the take(1) will not fire (and complete the observable stream) in case the original observable never emits. We have to make sure we only use it in cases where this can’t happen or provide additional unsubscription handling!
That take(1) still doesn’t unsubscribe when component is being destroyed. The subscription remains active until first value is emitted no matter if component is active or destroyed. So if we do something more crazy, like accessing the DOM, in our subscription — we might end up with an error in the console