В Angular останнім часом з’явилося багато нового: від сигналів до оновленого стилю для шаблонів. Тож я подумав, можливо, настав час поділитися оновленим підходом до code style?
Standalone
З випуском 19-ї версії всі компоненти, які ви створюєте, за замовчуванням стають standalone.
Якщо у вас версія нижче 19, я рекомендую створювати виключно standalone-компоненти, уникаючи використання модулів.
// Standalone-компонент для версій Angular < 19
@Component({
standalone: true,
selector: 'my-component',
template: `Hello World!`
})
// Standalone-компонент у Angular 19
@Component({
// прапор standalone більше не потрібен
selector: 'my-component',
template: `Hello World!`
})
Порада! У Angular 19 з’явився прапор компілятора, який видасть помилку, якщо виявить компонент, директиву або пайп, що не є standalone.
{
"angularCompilerOptions": {
"strictStandalone": true
}
}
Signals
Ну тут все зрозуміло: стараємось більше використовувати сигнали, computed, а також input() та output().
Ви ж розумієте, що команда фреймворка рухається в бік відмови від Zone.js, тому чим більше ви будете використовувати сигнали, тим менше потрібно буде рефакторити код у майбутньому.
Нова ера Lifecycle
Попрощайтеся з ngOnInit та ngAfterViewInit і оберіть більш спрощений підхід!
Ось базовий приклад як було раніше з хуком ngOnChanges:
@Component({
standalone: true,
selector: 'is-even',
template: `
Is Even: {{ isEven }}
`, changeDetection: ChangeDetectionStrategy.OnPush }) export class IsEvenComponent implements OnChanges { isEven: boolean | undefined; @Input({ required: true }) counter!: number; ngOnChanges(changes: SimpleChanges): void { if (changes['counter']) { this.isEven = changes['counter'].currentValue % 2 === 0; } } }
Тепер як це написати по новому:
@Component({
standalone: true,
selector: 'is-even',
template: `
Is Even: {{ isEven() }}
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class IsEvenComponent {
counter = input.required();
isEven = computed(() => this.counter() % 2 === 0);
}
Inject
Раніше для того щоб заюзати наш DI, потрібно було в конструкторі прописувати залежності, тепер у нас є новий метод inject!
// Раніше
constructor(private userService: UserService) {}
// Зараз
private readonly userService = inject(UserService)
Що він дає:
- inject зберігає правильний тип при використанні токенів
- inject можна використовувати в інших функціях, що дає змогу створювати композиційні функції
- При спадкуванні немає потреби передавати впроваджені сервіси до батьківського класу через виклик super в конструкторі.
Ін’єкція через конструктор залежить від прапора useDefineForClassFields, який потрібно явно встановити в значення false
Constructor
Окей, ми зрозуміли, що конструктор більше не є необхідним для inject. Але що ж робити з хуками afterRender та afterNextRender? А як щодо ефектів? Адже в документації зазначено, що їх потрібно інітити саме в конструкторі!?
@Component({ /* ... */ })
export class MyComponent {
private readonly logProductChanges = effect(() => { ...});
private readonly afterNextRenderRef = afterRender(() => { ...});
});
private readonly afterNextRender = afterNextRender(() => {...});
}
Бам! Такий підхід робить ваш код чистішим і організованішим.
RxJS
А що робити з кодом, який написаний на стрімах?
Просто конвертуємо його в сигнали!
@Component({
template: `{{ someValue() }}`
})
export class MyComponent {
someValue = toSignal(someStream$);
}
Шаблони
Раніше ми використовували директиви на кшталт ngIf, ngFor, ng-template для роботи з умовними виразами та рендерингом шаблонів.
ngIf
// Раніше
Content is visible
Placeholder
// Зараз
@if(isVisible) {
Content is visible
}@else {
Placeholder
}
ngFor
// Раніше
{{ item }}
// Зараз
@for (let item of items; track: item.id) {
{{ item }}
}
let
// Раніше
Hello, {{user.name}}
{{snack.name}}
Update profile
// Зараз
@let user = user$ | async;
@if(user) {
Hello, {{user.name}}
@for (snack of user.favoriteSnacks; track snack.id) {
{{snack.name}}
}
Update profile
}
Висновки
Просто дотримуйтесь цих порад, і буде вам щастя! 😄
Перекладено з: Оновлений Code Style для Angular