RxJS refactor: BehaviourSubjects

2018-06-03CODING ·angular rxjs behavioursubject

Using RxJS BehaviourSubjects to tidy up our code and remove tight coupling between the Template and the component, whilst also providing hooks to trigger data updates.In an earlier post I described how to leverage the power of RxJS and the combineLatest() and startWith() operators to tidy up my code. I have since updated the code on that page to reflect the new RxJS6 pipe operators.
I was never really 100% happy with this solution for two reasons in particular: I still had to subscribe to the results (this was in part resolved with my follow-up post, and I had to reference the ViewChild elements in the component. Although Angular allows us to reference the view elements using the ViewChild() operator, this isn’t the cleanest solution. 1) It tightly couples the view template to the implementation and logic of the component and 2) When looking at the template, the event bindings aren’t obvious.
Enter BehaviourSubjects.

It stores the latest value emitted to its consumers, and whenever a new Observer subscribes, it will immediately receive the “current value” from the BehaviorSubject.source

Kind of sounds like what we are trying to achieve with our searching and pagination. As a bonus, a BehaviourSubject takes an initial value. This now means that we can do away with our StartWith operator.

The original code currently looks like this
results-list.component.html

results-list.component.ts

items: result[];
totalPages: number;
readonly pageSize = 25;
@ViewChild(‘paginator’) paginator: PaginationComponent;
@ViewChild(‘filters’) filters: FilterComponent;

ngOnInit(){
const page$ = this.paginator.pageUpdated.pipe(
startWith(1),
tap(x= > this.currentPage = 1));
const filter$ = this.filter.filterUpdated.pipe(
startWith(”),
tap(x = > this.currentPage = x));

combineLatest(page$,filter$, (p,f) = > { return {page: p, filter: f}}).pipe(
debounceTime(200),
switchMap(r = > this.searchService.performSearch(r.page, r.filter)))
.subscribe(results = > {
this.items = results.items;
this.totalpages = results.totalItems / this.pageSize;});

}

Final code
results-list.component.html

results-list.component.ts

results$: Observable;
currentPage$: BehaviourSubject(1);
filter$: BehaviourSubject(null);

readonly pageSize = 25;

ngOnInit(){
items$ = combineLatest(page$,filter$, (p,f) = > { return {page: p, filter: f}}).pipe(
debounceTime(300),
switchMap(r = > this.searchService.performSearch(r.page, r.filter))
);
}

Another benefit this optimisation gives us is the ability to modify our filters and pagination from any source, not just from the components raising the original events.
results-list.component.ts

results$: Observable;
currentPage$: BehaviourSubject(1);
filter$: BehaviourSubject(null);
readonly pageSize = 25;

ngOnInit(){
items$ = combineLatest(page$,filter$, (p,f) = > { return {page: p, filter: f}}).pipe(
debounceTime(300),
switchMap(r = > this.searchService.performSearch(r.page, r.filter))
);
}

clearResults(){
this.currentPage$.next(1);
this.filter$.next(”);
}

Wow… From the original mess and duplicated code to this. Reactive programming and RxJS may have a steep learning curve, but once mastered can really tidy up your event based code. Thanks Ben and the RxJS team

Read More

Handling EF Core migrations in a team

Entity Framework Core has a good migration story for the Database, however it’s not perfect, especially in a large team where DB schema changes are frequent. All migrations are increamental based on the last *DbContextModelSnapshot.cs snapshot. When multiple migrations are happening in different branches, a lot of issues arise and we even asked EF team on Twitter on how to solve this problem. Click here for the tweet
To better understand the problem, let’s build up a scenario in team project.
Let’s see an example of problematic migration:

A
/
B1 C
| |
B2 |
/
(git merge)
|
D

The migration B1 and B2 are out-of-sync of migration C and when we merge B1, B2 and C back into main branch the D migration will likely not work from correct DbContext model snapshot.
Traditionally, we would revert C migration, merge branch with B1 and B2 migrations and re-apply C migration with correct DbContext model snapshot. Git doesn’t help us resolving the problem because there are no conflicts from the code perspective.
I found that the best way to solve this problem is to treat *DbContextModelSnapshot.cs as binary file, forcing the developers to resolve the problem before they break migration in master branch.
By including .gitattributes in Migrations folder with content:

PageDbContextModelSnapshot.cs binary

This will prevent any non-incremental changes to *DbContextModelSnapshot.cs and prevents breaking migrations after merging into master branch.
You can check my friends blog post on how to improve your team communication and resolve conflicts, once they happen at his blog Overcoming EF Core Migration Conflicts.

Read More