๐๐ฟ๐ผ๐บ ๐๐ณ-๐๐น๐๐ฒ ๐๐ผ ๐๐ฒ๐๐ถ๐ด๐ป ๐ฃ๐ฎ๐๐๐ฒ๐ฟ๐ป๐: ๐๐บ ๐๐ฐ๐ถ๐ณ๐ฏ๐ฆ๐บ ๐ช๐ฏ ๐๐ณ๐ช๐ต๐ช๐ฏ๐จ ๐๐ฆ๐ต๐ต๐ฆ๐ณ ๐๐ฐ๐ฅ๐ฆ

Table of contents
- ๐ Initial Approach: The If-Else Trap ๐คฆโโ๏ธ
- (I have shown only 3 charts but there were at least 8โ9 different kinds of custom components including charts, maps and cards etc.)
- ๐ Turning Point: Discovering Design Patterns through one of my mentors. Thatโs when I explored Design Patterns, and it completely changed how I approached the problem.
- ๐น Step 1: Create a Factory Service
- ๐น Step 2: Use the Factory in the Component

A few days ago, I was working on dynamically rendering different chart components in an Angular app.
These charts were being loaded from Micro Frontends as web components, and I needed to create them dynamically based on a config array.
๐ Initial Approach: The If-Else Trap ๐คฆโโ๏ธ
At first, I went with a basic approach, using multiple if-else or switch-case statements to check the โwidgetTypeโ and render the respective component.
(I have shown only 3 charts but there were at least 8โ9 different kinds of custom components including charts, maps and cards etc.)
<ng-container class="chart-container">
@if(widgetType === 'bar'){
<chs-bar-chart [data]="chartData"></chs-bar-chart>
}
@else if(widgetType === 'pie'){
<chs-pie-chart [data]="chartData"></chs-pie-chart>
}
@else if(widgetType === 'line'){
<chs-line-chart [data]="chartData"></chs-line-chart>
}
@else{
<div> No chart types found</div>
}
</ng-container>
It worked, but it wasnโt scalable. Every time a new chart type was added, I had to modify my code, making it harder to maintain. And my seniors kept saying this code is โTRASHโ and โSTOP USING IF-ELSE ๐ โ
๐ Turning Point: Discovering Design Patterns through one of my mentors. Thatโs when I explored Design Patterns, and it completely changed how I approached the problem.
So I used the :
โ
๐๐ฎ๐ฐ๐๐ผ๐ฟ๐ ๐ฃ๐ฎ๐๐๐ฒ๐ฟ๐ป to dynamically generate components based on the โwidgetTypeโ property.
โ
๐ฆ๐๐ฟ๐ฎ๐๐ฒ๐ด๐ ๐ฃ๐ฎ๐๐๐ฒ๐ฟ๐ป to handle different API configurations for each chart type.
The Factory Pattern allows us to create components dynamically without modifying the main logic.
๐น Step 1: Create a Factory Service
@Injectable({
providedIn: 'root'
})
export class ChartFactoryService {
constructor(private resolver: ComponentFactoryResolver) {}
createChart(widgetType: string, container: ViewContainerRef) {
container.clear();
const componentMap: { [key: string]: Type<any> } = {
bar: ChsBarChartComponent,
pie: ChsPieChartComponent,
line: ChsLineChartComponent
};
const component = componentMap[widgetType];
if (!component) {
console.error('Unknown chart type');
return;
}
const factory = this.resolver.resolveComponentFactory(component);
container.createComponent(factory);
}
}
Eliminates the if-else/switch case.
Easily extendableโโโjust update
componentMap
.
๐น Step 2: Use the Factory in the Component
@Component({
selector: 'app-dashboard',
template: `<ng-container #chartContainer></ng-container>`
})
export class DashboardComponent {
@ViewChild('chartContainer', { read: ViewContainerRef }) chartContainer!: ViewContainerRef;
constructor(private chartFactory: ChartFactoryService) {}
renderChart(widgetType: string) {
this.chartFactory.createChart(widgetType, this.chartContainer);
}
}
Now, adding a new chart type only requires adding it to the componentMap
, without modifying logic in multiple places.
โ Enhancing with Strategy Pattern
If different chart types require different API handling, we use the Strategy Pattern.
Step 1: Define an Interface for Chart Strategies
export interface ChartStrategy {
fetchData(apiUrl: string): Observable<any>;
}
Step 2: Implement Different Strategies for Each Chart
@Injectable({ providedIn: 'root' })
export class BarChartStrategy implements ChartStrategy {
constructor(private http: HttpClient) {}
fetchData(apiUrl: string): Observable<any> {
return this.http.get(`${apiUrl}/bar-chart-data`);
}
}
@Injectable({ providedIn: 'root' })
export class PieChartStrategy implements ChartStrategy {
constructor(private http: HttpClient) {}
fetchData(apiUrl: string): Observable<any> {
return this.http.get(`${apiUrl}/pie-chart-data`);
}
}
Step 3: Implement a Service to Pick the Right Strategy
@Injectable({
providedIn: 'root'
})
export class ChartDataService {
private strategies: { [key: string]: ChartStrategy } = {};
constructor(
barChartStrategy: BarChartStrategy,
pieChartStrategy: PieChartStrategy
) {
this.strategies['bar'] = barChartStrategy;
this.strategies['pie'] = pieChartStrategy;
}
getData(widgetType: string, apiUrl: string): Observable<any> {
const strategy = this.strategies[widgetType];
if (!strategy) {
console.error('No strategy found for', widgetType);
return of(null);
}
return strategy.fetchData(apiUrl);
}
}
Each chart type has its own API logic.
Decouples data fetching logic from the main component.
Step 4: Use the Strategy Service in Your Component
this.chartDataService.getData(widgetType, apiUrl).subscribe(data => {
console.log('Chart Data:', data);
});
Refactoring this felt like a game-changer. Now my code is:
โบ๏ธ More flexibleโโโI can add new charts without touching existing logic.
โบ๏ธ Easier to maintainโโโNo more messy if-else chains or Switch cases.
โบ๏ธ Optimized for Micro FrontendsโโโCharts load seamlessly as Web Components.
๐ Learnings: Good code isnโt just about making it workโ
Itโs about making it scalable and future-proof โ๏ธ
Even in frontend there are ways to ensure the code is not just basic โlogic and executeโ.
Subscribe to my newsletter
Read articles from Shanit Paul directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
