I have some model that tracks the "completed" status of a few pages (notice that none of the fields are optional, which is a must for the use case):
interface IncompletPages {
page1: boolean;
page2: boolean;
page3: boolean;
page4: boolean;
}
In my component, I have a local object of this interface which controls the active router-outlet
(i.e. it only enables the main router-outlet
once all pages are complete). By default, they all start as false, meaning the primary router-outlet
will be used, but at the creation of the component, an API call will return an instance of IncompletePages
which may have some of them set to true.
@Component({})
export class MyComponent {
pagesToComplete: IncompletPages = {
page1 = false,
page2 = false,
page3 = false,
page4 = false,
};
constructor(private mySvc: MyService) {
mySvc.getIncompletePages().subscribe({
next: (pages: IncompletePage) => {
this.pagesToComplete = pages;
},
});
}
}
From here I would like each of these pages to notify the pagesToComplete
object when they have completed by sending an object with their property and boolean value, but with only their property. To do this, I've added a simple getter and setter to my service:
export class MyService {
private complete$: Observable<Partial<IncompletePages>>;
private comleteSubscriber: Subscriber<Partial<IncompletePages>>;
constructor() {
this.complete$ = new Observable<Partial<IncompletePages>>(completeSubsciber => {
this.completeSubscriber = completeSubsciber;
});
}
getIncompletePages() {
// return result of API call
}
getCompletedNotification() {
return this.complete$;
}
setCompletedNotification(completed: Partial<IncompletePages>) {
this.completeSubscriber.next(completed);
}
}
So each page, once completed, should do something like mySvc.setCompletedNotification({page1: true});
when it has finished. This will emit on the complete$
observable in the service, which MyComponent
will pick up on with an open subscription to getCompletedNotification()
, as in the following:
@Component({})
export class MyComponent {
pagesToComplete: // ...
constructor(private mySvc: MyService) {
// ...
}
ngOnInit() {
this.mySvc
.getCompletedNotification()
.pipe(
tap((pages: Partial<IncompletePages>) => {
// And here's my question (below)
})
)
.subscribe();
}
}
Now with all that setup out of the way, my question is, is there a concise way I can update a single value in pagesToComplete
without rewriting the entire object? Something like this.pagesToComplete[keyof pages] = [keyof pages].value;
? I know that using <Partial<T>>
will make all properties of <T>
optional, and when sending a <Partial<T>>
, it will make all the other properties undefined. However, I don't want to reset the other values in my local object to undefined, but just update the one that was passed.
For now I've settled on re-writing the entire object every time with the help of the nullish coalescing operator:
(pages) => {
this.pagesToComplete = {
page1: pages.page1 ?? this.pagesToComplete.page1,
page1: pages.page2 ?? this.pagesToComplete.page2,
page1: pages.page3 ?? this.pagesToComplete.page3,
page1: pages.page4 ?? this.pagesToComplete.page4,
};
}
but this feels unnecessarily verbose.
If I understood you correctly, would this work?
@Component({})
export class MyComponent {
pagesToComplete: // ...
ngOnInit() {
this.mySvc
.getCompletedNotification()
.pipe(
tap((pages: Partial<IncompletePages>) => {
this.pagesToComplete = {...this.pagesToComplete, ...pages };
})
)
.subscribe();
}
}