I'm refactoring an Angular component to use signals throughout, but I'm stuck on how to properly initialize a signal that depends on required inputs without using ngOnInit. I am trying to see in this experiment that how it behaves without using any lifecycle hooks.
The processedData signal needs to be mutable, but its initial state must be derived from the required inputs which aren't available in the constructor.
What's the recommended Angular signals pattern for deriving initial signal state from required inputs while keeping the signal mutable for user interactions? I don't want to see this error if not using ngOnInit lifecycle:
RuntimeError: NG0950: Input "items" is required but no value is available yet. Find more at https://angular.dev/errors/NG0950
Current approach (works but uses ngOnInit):
import { Component, input, output, signal } from '@angular/core';
import { explicitEffect } from 'ngxtension/explicit-effect';
interface DataItem {
id: string;
name: string;
status: string;
}
type ProcessedData = Record<string, {
item: DataItem;
selected: boolean;
displayName: string;
}>;
@Component({
selector: 'app-example',
template: `<div>{{ processedData() | json }}</div>`
})
export class ExampleComponent {
items = input.required<DataItem[]>();
mode = input.required<'add' | 'remove'>();
processedData = signal<ProcessedData>({});
dataChanged = output<ProcessedData>();
constructor() {
explicitEffect([this.processedData], ([data]) =>
this.dataChanged.emit(data)
);
}
ngOnInit(): void { //trying to avoid lifecycles
this.processedData.set(
this.items().reduce((acc, item) => ({
...acc,
[item.id]: {
item,
selected: this.getInitialState(item),
displayName: item.name.toUpperCase()
}
}), {})
);
}
updateSelection(id: string): void {
this.processedData.update(data => ({
...data,
[id]: { ...data[id], selected: !data[id].selected }
}));
}
private getInitialState(item: DataItem): boolean {
return this.mode() === 'add'
? item.status === 'ACTIVE'
: item.status === 'FINAL';
}
}
Use linkedSignal, it derives its value based on a signal and also you can later change it or mutate it.
@Component({...})
export class Component {
items = input.required<DataItem[]>();
processedData = linkedSignal(() => this.items().reducer(...));
// mutate it whenever you need
someFunction() {
this.processedData.set(...);
}
}
With this approach, you can bypass the usage of ngOnInit.
Check the following demo
You can read more about it here