Reputation: 962
Assume I have an Angular app with two views. The first view shows a preview of some object, let's say a car, and the other view shows detailed information of that car. The car model would be something similar to:
export class Car {
model: string;
type: CarTypeEnum;
...;
}
Let's say I want both views to show me an Icon representing the car's type. The logic would be:
switch(someCar.type) {
case CarTypeEnum.HATCHBACK: return icons.hotHatch;
case CarTypeEnum.SEDAN: return icons.sedan;
case CarTypeEnum.SUV: return icons.suv;
case CarTypeEnum.COUPE: return icons.coupe;
case CarTypeEnum.VAN: return icons.van;
case CarTypeEnum.WAGON: return icons.wagon;
}
Where should this logic for getting the icon based on the car type go?
export class Car {
model: string;
type: CarTypeEnum;
...;
get typeIcon() {
// switch goes here
}
}
This feels somewhat right, given that I'm using this in two separate views, but it also feels like I'm polluting the model.
Should I add this code to a method and duplicate it into the two views component class? What if I had to use this code in 10 views, would I duplicate this logic 10 times in each of their respective component?
Should I create some static helper class containing a method with this logic, taking a car as a parameter, which I would then call in each component class?
Should I add the logic straight into the view?
<div *ngIf="car.type == carType.HATCHBACK" class="hatchback-icon-class"> ... </div>
<div *ngIf="car.type == carType.COUPE" class="coupe-icon-class"> ... </div>
...
Upvotes: 1
Views: 519
Reputation: 3842
My preferred strategy is to use services. You can either create it as a singleton so that when you load a car it's available for all components, or you can load it individually so that each component can load a different car.
Here's an example of what that looks like.
/services/car.service.ts
The service that loads a car from your data source and provides a standardized interface to all of your components
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
// If you want this to behave as a singleton, add {providedIn: 'root'} to @Injectable
@Injectable()
export class CarService {
private _car = new BehaviorSubject<any>(null);
private _carSnapshot;
// Method called by your components to load a specific car
load(carId: string): Promise<any> {
return this.getCarInfoFromWherever(carId);
}
// Returns an observable of the currently loaded car
car$(): Observable {
return this._car.asObservable();
}
// Method to retrieve the car data from whatever datasource you're using
getCarInfoFromWherever(carId: string): Promise<any> {
return new Promise(async (resolve: any) => {
// Retrieve the car information from wherever it is such as a database.
const carInfo = await DbGetCarInfo(carId);
// Set an object for easy access to current vehicle
this._carSnapshot = carInfo;
// Update your observable
this._car.next(carInfo);
});
}
// Example of abstraction to retrieve car attributes
getType(): string {
if (this._carSnapshot)
return this._carSnapshot['type'];
return null;
}
}
/components/main/main.component.ts
A component that wants to display a Ford Pinto
import { Component } from '@angular/core';
import { distinctUntilChanged } from 'rxjs/operators';
@Component({
selector: 'app-main',
templateUrl: './main.component.html',
styleUrls: ['./main.component.scss']
})
export class MainComponent {
private _subscription;
constructor(
// Inject the CarService into our component
public carSvc: CarService
) {
// Tell CarService which car to load
this.carSvc.load('FordPinto').then();
}
ngOnInit(): void {
// Subscribe to the car service observable
this._subscription = this.carSvc.car$()
.pipe(distinctUntilChanged())
.subscribe((car: any) => {
// The car has been loaded, changed, do something with the data.
console.log("Car Type:", this.carSvc.getType());
});
}
// Unsubscribe from the CarService observable
ngOnDestroy(): void {
this._subscription.unsubscribe();
}
}
/components/dashboard/dashboard.component.ts
A component that wants to display a Ferrari Testarossa
import { Component, OnInit } from '@angular/core';
import { distinctUntilChanged } from 'rxjs/operators';
@Component({
selector: 'app-dashboard',
// Here's an example of using the observable in a template
template: `<div>{{carSvc.car$() | json}}`,
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit, O {
constructor(
public carSvc: CarService
) {
this.carSvc.load('FerrariTestarossa').then();
}
ngOnInit(): void {
this.carSvc.car$()
.pipe(distinctUntilChanged())
.subscribe((car: any) => {
// Do something with the car information
});
}
// Unsubscribe from the CarService observable
ngOnDestroy(): void {
this._subscription.unsubscribe();
}
}
In this example two separate components load up to separate cars.
Upvotes: 0
Reputation: 1138
If you are going to use that icon all along the app, I would recommend creating a component with an input binding (as you say in your last paragraph).
Angular really encourages you to make presentational components that are easy to test and reuse, following KISS and SOLID principles.
More info in this article: https://indepth.dev/posts/1066/presentational-components-with-angular
Upvotes: 1