Компонент - это основной строительный блок приложения Angular. При работе с большим приложением очень важно иметь общие компоненты для общих функций. Ранее я написал статью о четырех практических советах по созданию хорошего общего компонента:
Здесь я собираюсь дать еще четыре совета по повышению качества компонентов.

5. Уничтожить входные данные объекта.
Обертывание нескольких аргументов в объект - это практика классического JavaScript. Тем не менее, это антипаттерн для компонента Angular.
export interface Configuration {
enableFilter: boolean;
enableSort: boolean;
...
}
@Component({
selector: 'custom-grid',
...
})
export class CustomGridComponent {
@Input() configuration: Configuration;
...
}
Основная проблема заключается в том, что это создает дополнительную сложность и требует поддержки кода. Это также затрудняет установку значения по умолчанию. Кроме того, его неудобно использовать, поскольку разработчикам приходится объявлять дополнительные переменные в своих представлениях.
@Component({
template: `
<custom-grid [configuration]="gridConfiguration"></custom-grid>
`,
...
})
export class TestComponent {
gridConfiguration: Configuration = {
enableFilter: true,
enableSort: true
}
...
}
Настоятельно рекомендуется всегда стараться деструктурировать входные данные объекта как можно более плоскими.
@Component({
selector: 'custom-grid',
...
})
export class CustomGridComponent {
@Input() enableFilter: boolean;
@Input() enableSort: boolean;
...
}
Теперь разработчики могут:
<custom-grid [enableFilter]="true" [enableSort]="true"></custom-grid>
Это не только делает его намного проще, но также позволяет нам указывать значение по умолчанию для каждого атрибута отдельно, как то, что я упоминал ранее в советах №1.
6. Замените входную мутацию на выходную привязку.
Если вы ненавидите компанию, в которой работаете, и хотите облажаться перед уходом, одна из лучших ловушек, которую вы можете сделать, - это обновить связанный объект @Input():
@Component({
selector: 'app-parent',
template: `
<div>Parent: {{ counterState | json }}</div>
<app-child [state]="counterState"></app-child>
`
})
export class ParentComponent {
counterState = { counter: 0 };
}
@Component({
selector: 'app-child',
template: `
<div>Child: {{ state | json }}</div>
<button (click)="onUpdateCounter()">Update Counter</button>
`
})
export class ChildComponent {
@Input() state: { counter: number };
onUpdateCounter() {
this.state.counter++;
}
}
Проблема обновления связанного объекта заключается в том, что это также повлияет на родительский объект. Однако нет никакой информации для других разработчиков, чтобы понять такое поведение при использовании компонента. Это может очень легко вызвать проблему, а также очень сложно отладить, поскольку логика обновления скрыта внутри компонента. Разработчики должны копаться в компонентах, которые они использовали, один за другим, прежде чем они наконец смогут узнать, где происходит обновление. Лучший подход - всегда использовать двустороннюю привязку всякий раз, когда есть обновление, которое выходит из компонента:
export interface CounterState {
counter: number;
}
@Component({
selector: 'app-parent',
template: `
<div>Parent: {{ counterState | json }}</div>
<app-child [(state)]="counterState"></app-child>
`
})
export class ParentComponent {
counterState: CounterState = { counter: 0 };
}
@Component({
selector: 'app-child',
template: `
<div>Child: {{ state | json }}</div>
<button (click)="onUpdateCounter()">Update Counter</button>
`
})
export class ChildComponent {
@Input() state: CounterState;
@Output() stateChange = new EventEmitter<CounterState>();
onUpdateCounter() {
this.stateChange.emit({
...this.state,
counter: this.state.counter + 1
});
}
}
Для других разработчиков гораздо яснее, что компонент обновит связанную переменную, поскольку теперь это явно двусторонняя привязка (он заблокирует обновление, если они попытаются выполнить одностороннюю привязку с <app-child [state]=”counterState”></app-child>).
Кстати, гораздо безопаснее обновлять примитивный ввод, поскольку он не будет отражен в родительском элементе (@Input counter: number;). Тем не менее, по-прежнему настоятельно рекомендуется использовать двустороннюю привязку для всех случаев входной мутации для обеспечения согласованности данных и удобства обслуживания.
7. Извлечь вызовы API в компонент оболочки.
Хотя это не официальная практика, по моему опыту, она весьма полезна для улучшения возможности повторного использования компонентов. При работе с компонентами более высокого уровня с большей бизнес-логикой внутри обычно есть вызовы API:
@Component({
template: `
<div class="grid">
<div class="row">
<div *ngFor="let product of products"
class="col-6 col-md-4 mb-2">
<div class="card bg-white" (click)="navigateTo(product)">
<div class="card-header">
<img [src]="product.img">
</div>
<div class="card-body">
<strong>{{product.name}}</strong>
<div>Brand: {{product.brand}}</div>
<div>Color: {{product.color}}</div>
<div>Style: {{product.style}}</div>
</div>
<div class="card-footer text-center">
<span class="price">${{product.price}}</span>
</div>
</div>
</div>
</div>
</div>
`,
...
})
export class ProductOverviewComponent {
products: Product[] = [];
constructor(productService: ProductService) {
productService.getAllProducts()
.subscribe(products => this.products = products);
}
...
}
Мы можем извлечь вызовы API в компонент-оболочку, чтобы внутренний компонент можно было сохранить презентационным.
@Component({
selector: 'product-grid',
template: `
<div class="grid">
<div class="row">
<div *ngFor="let product of products"
class="col-6 col-md-4 mb-2">
<div class="card bg-white" (click)="navigateTo(product)">
<div class="card-header">
<img [src]="product.img">
</div>
<div class="card-body">
<strong>{{product.name}}</strong>
<div>Brand: {{product.brand}}</div>
<div>Color: {{product.color}}</div>
<div>Style: {{product.style}}</div>
</div>
<div class="card-footer text-center">
<span class="price">${{product.price}}</span>
</div>
</div>
</div>
</div>
</div>
`,
...
})
export class ProductGridComponent {
@Input() products: Product[] = [];
...
}
@Component({
template: `
<product-grid [products]="products"></product-grid>
`,
...
})
export class ProductOverviewComponent {
products: Product[] = [];
constructor(productService: ProductService) {
productService.getAllProducts()
.subscribe(products => this.products = products);
}
}
Основное преимущество презентационных компонентов состоит в том, что они облегчают повторное использование кода. Намного проще создать новый компонент поверх существующего презентационного компонента, поскольку нам не нужно обрабатывать скрытую внутреннюю логику. Тем не менее, этот подход имеет недостаток в виде большего количества кода и, следовательно, большей сложности. Следовательно, рекомендуется использовать его только в больших проектах, в которых несколько разработчиков работают над пулом компонентов.
Кстати, если вы хотите узнать больше о повторном использовании компонентов, здесь я написал еще одну статью о методах повторного использования существующего компонента:
8. Используйте подход одного файла для простых компонентов
Не все компоненты сложны. Есть несколько простых компонентов, в которых всего несколько строк кода и стилей. Рекомендуется использовать однофайловый подход для таких компонентов:
@Component({
template: `
<div class="card">
<div class="card-header">
{{displayText}}
</div>
</div>
`,
styles: [`
.card-header {
color: green;
}
`]
})
export class CardComponent {
...
}
Это делается для того, чтобы сделать его более удобным для разработчиков, поскольку разработчики могут просто сохранить отдельный файл в разделенном виде во время разработки без необходимости переключаться между ними.
Спасибо за прочтение! Надеюсь, эта статья окажется для вас полезной. Будем очень признательны за любые комментарии. : D