Новий Input Signal: всебічне порівняння з традиційним Input

У цьому матеріалі порівнюються два підходи до використання властивостей 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