2

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';
  }
}
4
  • please share stackblitz or github repo with the issue happening Commented Oct 22 at 17:47
  • the error is for Input "selectionData" is required but the code is something different! Commented Oct 22 at 17:58
  • Well updated error, but basic idea is how to make those required inputs available without using ngOninit(). And Computed seems not working as well in my case Commented Oct 22 at 19:03
  • please share stackblitz or github repo with issue happening for debugging Commented Oct 22 at 19:25

1 Answer 1

2

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

Sign up to request clarification or add additional context in comments.

3 Comments

Interesting , I read similar approach here "riegler.fr/blog/2024-12-31-lifecycle-hook-less " However in your above answer when I try to use "processedData()" in template I still see "required inputs not available error"
Can you check again? I have added a stackblitz example with a required input and it doesn't display any error on the template HTML.
Yup, I think this is right. I missed some config earlier but using linked signals here totally make sense to me. Thanks

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.