I'm developing an angular 7 app with a multi-tab layout. Each tab contains a component, that can reference other nested components.
When the user select a new / another tab, the components displayed on the current tab are destroyed (I'm not just hiding them and I've followed this pattern since the opened tabs could be many and it seems a faster solution to me). Since loading a component can be costly (possibly lot of data retrieved from API) I'm trying to persist each component state in the tab container (it stays alive for the entire application lifetime).
Everything is working as expected but I think I've used a lot of code to accomplish my needs and I hope that everything could be simpler (and less prone to errors).
Here below an extract about how I managed to do it. First of all I created an abstract class that every component in the tab should extend.
export abstract class TabbedComponent implements OnInit, OnDestroy {
abstract get componentName(): string;
tab: LimsTab;
host: TabComponent;
private _componentInitialized = false;
constructor(
private _host: TabComponent,
) {
this.host = _host;
}
get componentInitialized(): boolean {
return this._componentInitialized;
}
ngOnInit(): void {
this.tab = this.host.tab;
this.loadDataContext();
this._componentInitialized = true;
}
ngOnDestroy(): void {
this.saveDataContext();
}
get dataContext() {
return this.tab.dataContext;
}
protected abstract saveDataContext(): void;
protected abstract loadDataContext(): void;
}
loadDataContext and saveDataContext are implemented in the concrete component and contains the logic to save / retrieve the component instance values. Moreover I've used the _componentInitialized to understand when the component has called its life cycle hook OnInit: components can receive @Inputs from their father and ngOnChanges is triggered before OnInit from what I saw.
Here an example of the concrete implementation:
@Component({
...
})
export class MyComponent extends TabbedComponent implements OnChanges {
private qualityLotTests: QualityLotTestListItem[];
private _selectedTest: QualityLotTestListItem;
@Input()
selectedQualityLot: string;
@Output()
selectedTest: EventEmitter<QualityLotTestListItem> = new EventEmitter<QualityLotTestListItem>();
constructor(_host: TabComponent, private qualityLotService: QualityLotService) {
super(_host);
}
get componentName(): string {
return 'QualityLotTestListComponent';
}
ngOnChanges(changes: SimpleChanges): void {
if (this.componentInitialized) {
// before calling loadData I need to restore previous data (if any)
// this allow me to undestrand if loaded data filters has changed
// I know I restored it if onInit has executed
// if componentInitialized -> onInit has been executed and input parameters passed from parent are changed
this.loadData();
}
}
protected saveDataContext(): void {
this.dataContext[this.componentName].selectedQualityLot = this.selectedQualityLot;
this.dataContext[this.componentName].state = this.state;
this.dataContext[this.componentName].qualityLotTests = this.qualityLotTests;
this.dataContext[this.componentName].selectedSampleTestIDs
= this.selectedSampleTestIDs;
this.dataContext[this.componentName]._selectedTest = this._selectedTest;
}
protected loadDataContext(): void {
let previouslySelectedQualityLot: string;
if (this.dataContext[this.componentName]) {
previouslySelectedQualityLot = this.dataContext[this.componentName].selectedQualityLot;
this.state = this.dataContext[this.componentName].state || this.state;
this.qualityLotTests = this.dataContext[this.componentName].qualityLotTests;
this._selectedTest = this.dataContext[this.componentName]._selectedTest;
} else {
this.dataContext[this.componentName] = {};
}
this.selectedTest.emit(this._selectedTest);
if (this.qualityLotTests && previouslySelectedQualityLot === this.selectedQualityLot) {
this.dataStateChange(<DataStateChangeEvent>this.state);
} else {
this.loadData();
}
}
loadData(): void {
if (this.selectedQualityLot) {
this.loading = true;
this.qualityLotService
.getQualityLotTests(this.selectedQualityLot)
.subscribe(
qualityLotTests => {
this.qualityLotTests = qualityLotTests;
this.gridData = process(this.qualityLotTests, this.state);
this.loading = false;
},
error => (this.errorMessage = <any>error)
);
} else {
this.qualityLotTests = null;
this.gridData = null;
this.state = {
skip: 0,
take: 10
};
this.selectedSampleTestIDs = [];
this.selectedTest.emit(null);
}
}
public dataStateChange(state: DataStateChangeEvent): void {
this.loading = true;
this.state = state;
this.gridData = process(this.qualityLotTests, this.state);
this.loading = false;
}
}
How do you think I can improve the code above?
I've loaded an extract of my app here https://stackblitz.com/edit/angular-tabbed-app