Signal Input là cách Angular nâng cấp @Input() truyền thống, biến nó thành một Signal thay vì giá trị tĩnh như ngày xưa. Ra mắt từ phiên bản 17.1 (khoảng cuối 2023), nó cho phép component con nhận dữ liệu động từ component cha dưới dạng Signal, tự động cập nhật khi cha thay đổi mà không cần Zone.js quét mệt nghỉ. Nói đơn giản, nó giống như thằng giao hàng mới trong công ty:
– “Sếp đổi hàng, tao giao ngay, không cần mày hỏi lại!”
Signal Input là gì?
- Cũ:
@Input()nhận giá trị tĩnh từ component cha (string, number, object), muốn đổi thì cha phải truyền lại, con phải tự phát hiện quangOnChangeshoặc Zone.js. - Mới: Signal Inputs nhận một Signal từ cha, giá trị thay đổi tự động truyền xuống con mà không cần “lắm drama”. Bạn khai báo bằng
input()thay vì@Input().
// Component con
import { Component, input } from '@angular/core';
@Component({
selector: 'app-child',
template: `<p>Số dư: {{ money() }}</p>`
})
export class ChildComponent {
money = input<number>(0); // Signal Input, default là 0
}
// Component cha
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<app-child [money]="money"></app-child>
<button (click)="money.set(100)">Cho 100k</button>
`
})
export class ParentComponent {
money = signal(50); // Signal truyền xuống con
}
Kết quả: Ban đầu hiển thị “Số dư: 50”, nhấn nút thì thành “Số dư: 100”, con tự biết mà không cần cha hét lên: “Ê, tao đổi rồi đấy!”
Các tính năng chính của Signal Input
Default Value: Bạn có thể gán giá trị mặc định nếu cha không truyền.
age = input<number>(18); // Nếu cha không truyền, mặc định 18
Required: Buộc cha phải truyền, không thì báo lỗi compile-time.
id = input.required<string>(); // Cha quên truyền, Angular đỏ mặt!
Tự động cập nhật: Không cần ngOnChanges, giá trị đổi là UI đổi luôn.
balance = input<number>(0);
// balance() tự đổi khi cha gọi balance.set(...)
Type Safety: Hỗ trợ generic, đảm bảo type đúng ngay từ đầu.
score = input<number[]>([]); // Chỉ nhận mảng số
Transform Signal Input (Tính năng thử nghiệm)
Một số tài liệu thử nghiệm (Angular RFCs) đề cập khả năng transform giá trị Signal Input bằng tùy chọn transform. Tuy nhiên, tính đến phiên bản 17.x (tháng 3/2025), nó chưa chính thức, chỉ là ý tưởng.
money = input<number>({ transform: (val: string) => parseInt(val) });
// Chưa hỗ trợ chính thức
Nếu được triển khai, nó sẽ cho phép cha truyền kiểu khác (như string) và con tự convert sang kiểu mong muốn (number). Hiện tại, bạn phải tự làm trong Computed Signal.
So sánh với @Input() cũ và input()
| Tiêu chí | @Input() | Signal Input |
|---|---|---|
| Kiểu dữ liệu | Giá trị tĩnh | Signal động |
| Phát hiện thay đổi | Zone.js hoặc ngOnChanges | Reactivity tự động |
| Cú pháp | @Input() x: number | x = input<number>() |
| Required | Không hỗ trợ trực tiếp | Có input.required() |
| Hiệu suất | Quét toàn bộ | Chỉ cập nhật chỗ cần |
| Read-only | writeable | read-only |
Signal Inputs là Read-Only: “Giao hàng không nhận chỉnh sửa”
Khi bạn khai báo một Signal Input bằng input() hoặc input.required(), nó trả về một Signal mà component con chỉ có thể đọc giá trị inputName(), chứ không thể ghi (dùng set hoặc update). Điều này có nghĩa là:
- Component con không thể tự thay đổi giá trị của Signal Input.
- Việc thay đổi giá trị chỉ có thể được thực hiện từ component cha, nơi Signal gốc được tạo ra.
// Component con
import { Component, input } from '@angular/core';
@Component({
selector: 'app-child',
template: `<p>Số dư: {{ money() }}</p>`
})
export class ChildComponent {
money = input<number>(0); // Signal Input, mặc định 0
tryToChange() {
this.money.set(100); // LỖI! "Property 'set' does not exist on type 'InputSignal'"
}
}
Angular sẽ báo lỗi ngay lúc biên dịch (compile-time) vì money là một InputSignal, không phải WritableSignal. Nó không có phương thức set hay update!
Signal Input như thằng giao hàng ngoan: “Sếp đưa tao 50k, tao giao mày 50k, mày đòi sửa thành 100k thì tao không có quyền, kêu sếp đi!”
Tại sao Signal Inputs là Read-Only?
- Thiết kế có chủ đích: Signal Inputs được tạo ra để đảm bảo single source of truth (nguồn dữ liệu duy nhất). Giá trị đến từ cha, và chỉ cha mới được quyền thay đổi. Nếu con tự sửa, sẽ gây rối loạn – như kiểu bạn đi chợ, con bạn ở nhà tự ý đổi tiền trong ví!
- Reactivity an toàn: Signal Inputs dựa vào hệ thống reactivity của Angular. Nếu con tự sửa Signal, hệ thống không biết ai là “chủ thật sự”, dẫn đến lỗi khó lường.
- Khác với Writable Signal:
–signal(0)tạo mộtWritableSignal(có thể đọc và ghi).
–input(0)tạo một InputSignal (chỉ đọc trong con, ghi từ cha).
Kết hợp với Computed Signal để “biến hóa” trong con
Vì Signal Inputs là read-only, nếu component con muốn “chơi” với giá trị từ cha, nó phải dùng Computed Signal. Computed Signal cũng là read-only, nhưng cho phép bạn tạo giá trị mới dựa trên Signal Input:
// Component con
import { Component, input, computed } from '@angular/core';
@Component({
selector: 'app-status-box',
template: `
<div [class]="statusClass()">
Số dư: {{ balance() }} VNĐ
</div>
`]
})
export class StatusBoxComponent {
balance = input.required<number>(); // Signal Input từ cha
threshold = input(100); // Signal Input từ cha
// Computed Signal tính class
statusClass = computed(() => {
return this.balance() < this.threshold() ? 'danger' : 'safe';
});
}
Signal Input là “phiên bản Pro” của @Input(), nhanh, nhẹ, và tự động. Kết hợp với Computed Signal, nó biến component con thành “trợ lý thông minh” – nhận đồ từ cha, tự tính toán, và bung lụa kiểu gì cũng đẹp!