Table of contents
The old way to update the object
Hey there! Can you take a look at the code below and answer truly?
- Is the code below resemble yours?
- Do you still update the object in this fashion?
- Have you ever thought about whether there is a better way to do this?
If your answer is yes, then you got in the right place! This article will discover a way better pattern to use for object updates.
import { ReplaySubject } from 'rxjs';
export interface Car {
id: number;
make: string;
color: string;
}
export class CarService {
private carRplSubj = new ReplaySubject<Car>(1);
car$ = this.carRplSubj.asObservable();
updateCar(carPart: Car): void {
this.carRplSubj.next(carPart);
}
}
const carService = new CarService();
carService.car$.subscribe(console.log);
carService.updateCar({ make: 'BMW', color: 'White', id: 1 });
Do you want to follow along?
The final version can be found on GitHub.
Remove everything from the
Otherwise, you might set up a new TypeScript project from the scratch.
What puzzles are we trying to solve?
- Usually, we don’t want to update all fields at the same time.
- Only some properties have to be set to defaults.
- Passing the whole object whenever we want to modify a particular part isn’t necessary.
- We want to get rid of an additional object reference, which duty is to gather data from the form. Better to have one and for all. Our mission is the SRP (Single Responsibility Principle).
So what's the solution?
The solution is in a combination of
scan((oldValue: Partial<Car>, newValue: Partial<Car>) => {
return { ...oldValue, ...newValue };
})
Basically, the
When we have an interface or type with required fields,
a
Now let’s apply our solution to the existing code.
import { BehaviorSubject } from 'rxjs';
import { scan } from 'rxjs/operators';
export interface Car {
id: number;
make: string;
color: string;
}
export class CarService {
private carBhvSubj = new BehaviorSubject<Partial<Car>>({ color: 'White' });
car$ = this.carBhvSubj.asObservable()
.pipe(
scan((oldValue: Partial<Car>, newValue: Partial<Car>) => {
return { ...oldValue, ...newValue };
})
);
updateCar(carPart: Partial<Car>): void {
this.carBhvSubj.next(carPart);
}
}
const carService = new CarService();
carService.car$.subscribe(console.log);
carService.updateCar({ make: 'BMW' });
Alright, looks like we made it.
As of now, we can pass partial objects into
Let's make it reusable
To make it reusable, we need to extract our
Here is a little case study of this strategy in action:
import { BehaviorSubject, OperatorFunction } from 'rxjs';
import { scan } from 'rxjs/operators';
export interface Car {
id: number;
make: string;
color: string;
}
export function assign<T>(): OperatorFunction<Partial<T>, Partial<T>> {
return scan((oldValue: Partial<T>, newValue: Partial<T>) => {
return { ...oldValue, ...newValue };
});
}
export class CarService {
private carBhvSubj = new BehaviorSubject<Partial<Car>>({ color: 'White' });
car$ = this.carBhvSubj.asObservable().pipe(assign());
updateCar(carPart: Partial<Car>): void {
this.carBhvSubj.next(carPart);
}
}
const carService = new CarService();
carService.car$.subscribe(console.log);
carService.updateCar({ make: 'BMW' });
Let's add validation
Most likely, you will use this strategy along with the form. Which means that you’ll have a submit button. So, it naturally leads us to do form validations. Let’s add one, and see it in action:
import { BehaviorSubject, OperatorFunction } from 'rxjs';
import { scan, map } from 'rxjs/operators';
export interface Car {
id: number;
make: string;
color: string;
}
export function assign<T>(): OperatorFunction<Partial<T>, Partial<T>> {
return scan((oldValue: Partial<T>, newValue: Partial<T>) => {
return { ...oldValue, ...newValue };
});
}
export class CarService {
private carBhvSubj = new BehaviorSubject<Partial<Car>>({ color: 'White' });
car$ = this.carBhvSubj.asObservable().pipe(assign());
isCarValid$ = this.car$
.pipe(
map((car: Partial<Car>) => {
return Boolean(car.color && car.make);
})
);
updateCar(carPart: Partial<Car>): void {
this.carBhvSubj.next(carPart);
}
}
const carService = new CarService();
carService.car$.subscribe(console.log);
carService.isCarValid$.subscribe(console.log);
carService.updateCar({ make: 'BMW' });
Now, we have everything in place.
The only update that you might do is to extract the
type PartialCar = Partial<Car>;