在 Angular 应用程序中的何处编写 UI 逻辑?
Where to write UI logic in an Angular app?
假设我有一个有两个视图的 Angular 应用程序。第一个视图显示某些对象的预览,比方说一辆车,另一个视图显示该车的详细信息。汽车模型类似于:
export class Car {
model: string;
type: CarTypeEnum;
...;
}
假设我希望两个视图都向我显示一个代表汽车类型的图标。逻辑是:
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;
}
根据车型获取图标的逻辑应该放在哪里?
- 我应该在汽车模型 class 中创建一个 属性 吗?
export class Car {
model: string;
type: CarTypeEnum;
...;
get typeIcon() {
// switch goes here
}
}
鉴于我在两个单独的视图中使用它,这感觉有点正确,但它也感觉我在污染模型。
我是否应该将此代码添加到方法中并将其复制到两个视图组件中 class?如果我必须在 10 个视图中使用这段代码,我会在它们各自的每个组件中重复这个逻辑 10 次吗?
我是否应该创建一些包含具有此逻辑的方法的静态助手 class,将汽车作为参数,然后我将在每个组件中调用它 class?
我应该直接将逻辑添加到视图中吗?
<div *ngIf="car.type == carType.HATCHBACK" class="hatchback-icon-class"> ... </div>
<div *ngIf="car.type == carType.COUPE" class="coupe-icon-class"> ... </div>
...
- 我是否应该生成一个将汽车类型作为输入的 Angular 组件?这将是可重用的,但只是为了渲染这个图标有点矫枉过正,不是吗?
如果您要在整个应用程序中使用该图标,我建议您创建一个具有输入绑定的组件(如您在上一段中所说)。
Angular 真正鼓励您按照 KISS 和 SOLID 原则制作易于测试和重用的展示组件。
本文中的更多信息:https://indepth.dev/posts/1066/presentational-components-with-angular
我的首选策略是使用服务。您可以将其创建为单例,以便在加载汽车时它可用于所有组件,或者您可以单独加载它,以便每个组件可以加载不同的汽车。
这是一个示例。
/services/car.service.ts
从您的数据源加载汽车并为您的所有组件提供标准化接口的服务
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
想要显示福特 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
想要显示法拉利 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();
}
}
在此示例中,两个单独的组件加载到不同的汽车上。
假设我有一个有两个视图的 Angular 应用程序。第一个视图显示某些对象的预览,比方说一辆车,另一个视图显示该车的详细信息。汽车模型类似于:
export class Car {
model: string;
type: CarTypeEnum;
...;
}
假设我希望两个视图都向我显示一个代表汽车类型的图标。逻辑是:
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;
}
根据车型获取图标的逻辑应该放在哪里?
- 我应该在汽车模型 class 中创建一个 属性 吗?
export class Car {
model: string;
type: CarTypeEnum;
...;
get typeIcon() {
// switch goes here
}
}
鉴于我在两个单独的视图中使用它,这感觉有点正确,但它也感觉我在污染模型。
我是否应该将此代码添加到方法中并将其复制到两个视图组件中 class?如果我必须在 10 个视图中使用这段代码,我会在它们各自的每个组件中重复这个逻辑 10 次吗?
我是否应该创建一些包含具有此逻辑的方法的静态助手 class,将汽车作为参数,然后我将在每个组件中调用它 class?
我应该直接将逻辑添加到视图中吗?
<div *ngIf="car.type == carType.HATCHBACK" class="hatchback-icon-class"> ... </div>
<div *ngIf="car.type == carType.COUPE" class="coupe-icon-class"> ... </div>
...
- 我是否应该生成一个将汽车类型作为输入的 Angular 组件?这将是可重用的,但只是为了渲染这个图标有点矫枉过正,不是吗?
如果您要在整个应用程序中使用该图标,我建议您创建一个具有输入绑定的组件(如您在上一段中所说)。
Angular 真正鼓励您按照 KISS 和 SOLID 原则制作易于测试和重用的展示组件。
本文中的更多信息:https://indepth.dev/posts/1066/presentational-components-with-angular
我的首选策略是使用服务。您可以将其创建为单例,以便在加载汽车时它可用于所有组件,或者您可以单独加载它,以便每个组件可以加载不同的汽车。
这是一个示例。
/services/car.service.ts
从您的数据源加载汽车并为您的所有组件提供标准化接口的服务
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
想要显示福特 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
想要显示法拉利 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();
}
}
在此示例中,两个单独的组件加载到不同的汽车上。