У цьому матеріалі порівнюються два підходи до використання властивостей input
у Angular: традиційний декоратор @Input()
і новий підхід з використанням input signal
.
У традиційному підході ми використовуємо декоратор @Input()
для передачі значень між компонентами. Це дозволяє легко змінювати значення властивості програми:
ts
@Input() myInputProp: boolean = false;
performUpdate() {
this.myInputProp = true;
}
В той час як у новому підході, який використовує input signal
, зміна значення програми вже не відбувається так легко:
ts
myInputProp = input(false);
performUpdate() {
// немає методу для оновлення значення сигналу myInputProp
// це не writable сигнал (тільки для читання всередині компонента)
}
Це зменшує можливість програмно оновлювати значення, що є частиною односпрямованого потоку даних і декларативної парадигми, яка є більш ефективною.
З традиційним підходом можна отримати значення з @Input()
і обчислити значення на основі цього:
ts
@Input() username: string;
get initials(value: string) {
return this.extractInitials(value);
}
Але за допомогою input signal
цей процес виглядає трохи по-іншому:
ts
username = input();
initials = computed(() => {
return this.extractInitials(this.username());
});
Використання initials
у шаблоні:
ts
{{ initials() }}
У традиційному підході функція extractInitials
викликається на кожному циклі детекції змін, але з новим підходом з input signal
і computed
функція викликається лише коли змінюється значення username
(в тому числі і при першому виклику).
Щодо побічних ефектів при зміні Input
, то з традиційним підходом можна використовувати ngOnChanges
або підходи з getter
і setter
:
ts
@Input() username: string;
ngOnChanges(changes: SimpleChanges) {
if (changes['username']) {
this.onUsernameChange(this.username); // побічний ефект
}
}
З новим підходом у Angular є спеціальний API effect
:
ts
username = input();
constructor() {
effect(() => {
this.onUsernameChange(this.username()); // побічний ефект
});
}
Крім того, у тестуванні компонентів з властивостями Input
із традиційним підходом все набагато простіше:
ts
it('should set initials properly', () => {
componentInstance.username = 'Ankit Kaushik';
fixture.detectChanges();
expect(componentInstance.initials).toBe('AK');
});
А ось з новим підходом:
ts
it('should set initials properly', () => {
fixture.componentRef.setInput('username', 'Ankit Kaushik');
expect(componentInstance.initials()).toBe('AK');
});
Звісно, двостороння прив'язка також підтримується як у традиційному підході:
ts
@Component({ selector: 'custom-slider', /* ... */ })
export class CustomSlider {
@Input() value;
// output має бути стратегічно названим
// для підтримки двосторонньої прив'язки
// ${input-name}Change
@Output() valueChange = new EventEmitter();
}
@Component({ template: `` })
export class MediaControls {
volume = 0;
}
Так і в новому підході, використовуючи model()
:
ts
@Component({ /* ... */})
export class CustomSlider {
// Визначаємо модельний input під назвою "value".
value = model(0);
increment() {
// Оновлюємо модельний input з новим значенням, пропагуючи значення до всіх прив'язок.
}
}
this.value.update(oldValue => oldValue + 10);
}
Цей новий підхід з input signal
надає більш чистий і декларативний підхід до взаємодії з даними. Окрім того, він значно зменшує кількість повторень і складність у коді.
Перекладено з: New Input Signal: A comprehensive comparison with the traditional Input