Facade Pattern for rxjs streams for cleaner Angular architecture?

171 Views Asked by At

For context:

I'm working on an Angular 15 app, where I distinguish between smart and dumb components. My smart components do have connection to services, whereas the dumb components only communicate via Inputs and Outputs with the smart component and do not get any services injected.

Within the smart components I use a reactive declarative (rxjs) approach to react to events. However, by now these smart components involve many reactive streams which makes the component bloated.

As these smart components are kind of "collectors" of many events and the "access" to the api service, I don't see reasonable way how I could break up the component into smaller parts.

Question:

I was thinking about using Design Patterns, e.g. the Facade Pattern, to declutter the component by putting the reactive streams into a separate service and the component would only have to subscribe to the final observables and handle the side effects (like UI updates).

However, I wonder if this would be a smart approach and if there are better ways to deal with this situation.

Example of complex smart component:

class MySmartComponent {
  // ... several @Input and private members

  // Multiple RxJS streams to handle different scenarios
  dataFromRoute$: Data[] = this.route.url.pipe(/* ... */);
  dataAfterAdded$: Data[] = this.addDataButtonClick$.pipe(/* ... */);
  dataAfterMore$: Data[] = this.getMoreDataButtonClick$.pipe(/*...*/);
  dataAfterDeleted$: Data[] = this.deleteDataButtonClick$.pipe(/* ... */);

  data$: Observable<Data[]> = merge(
        this.dataFromRoute$,
        this.dataAfterAdded$,
        this.dataAfterMore$
        this.dataAfterDeleted$,
    ).pipe(/*...*/);

  searchInput$ = new BehaviorSubject<string>(''); // user can search/filter Data

  // subscription only to this Observable via async pipe in template!
  filteredData$: Observable<Data[] | null> = combineLatest([
        this.data$,
        this.dataSearchInput$,
    ]).pipe(

  // function calls by child components
  onSearchInputChange(value: string) { /* ... */ }
  onDeleteDataClick(event: SomeEvent) { /* ... */ }
  // ... more logic
}
1

There are 1 best solutions below

12
alexdefender93 On

Generally using a separate service for managing data is a valid approach. You can create "MySmartService", make it non-singleton, and declare it as a provider for MySmartComponent. For example:

@Injectable()
export class MySmartService {
   ...
}

@Component({
    providers: [MySmartService]
})
class MySmartComponent {
    constructor(private mySmartService: MySmartService) {}
}

This way your MySmartService will only be created when MySmartComponent is created. Also, you can add ngOnDestroy lifecycle hook to MySmartService to complete your data streams. It's going to work because MySmartService will be destroyed along with MySmartComponent.

I wouldn't call it Facade thought. Facade pattern is a structured API for several services or some complicated structure. In this case, we only have one component with different API calls / data streams.

Perhaps it makes sense to create several additional services - one for API , one for user events, etc. So every service has one purpose and follows single responsibility principle.