Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide input property to options of createComponent #55032

Closed
7schmiede opened this issue Mar 25, 2024 · 4 comments
Closed

Provide input property to options of createComponent #55032

7schmiede opened this issue Mar 25, 2024 · 4 comments
Labels
area: core Issues related to the framework runtime core: inputs / outputs cross-cutting: signals needs reproduction This issue needs a reproduction in order for the team to investigate further
Milestone

Comments

@7schmiede
Copy link

Which @angular/* package(s) are relevant/related to the feature request?

core

Description

Modals are usually created using createComponent.
If we open a component that has signal inputs, we can assign a value using setInput on the ComponentRef.

But if we use input.required and access the signal directly in the component, the error "NG0950: Input is required but no value is available yet." is thrown.

So setInput will only be resolved with the next change detection.

Proposed solution

Is it possible to provide another property input on the options of createComponent to pass the required inputs directly?
createComponent should extract required inputs from the component and then require the options.input.

Example usage:

@Component()
class FooComponent {
    bar = input.required<boolean>();
}

viewContainerRef.createComponent(FooComponent, {
    input: {
        bar: true
    }
})

This code snipped could be used to extract the signals from the component.
Only the optional inputs should be listed with a ?.

type InputSignalsOf<T> = {
    [K in keyof T as T[K] extends InputSignal<unknown> ? K : never]: T[K] extends InputSignal<infer TValue> ? TValue : never;
};

Alternatives considered

A workaround is to wrap the signal with computed - but this is not a clean solution.

@ngbot ngbot bot modified the milestone: needsTriage Mar 25, 2024
@alxhub
Copy link
Member

alxhub commented Mar 25, 2024

This should work fine with input.required:

const ref = viewContainerRef.createComponent(FooComponent);
ref.setInput('bar', true);

Change detection won't run immediately upon creating the component, so you have time to set the inputs.

@alxhub alxhub added the needs reproduction This issue needs a reproduction in order for the team to investigate further label Mar 25, 2024
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Mar 25, 2024
@fen89
Copy link

fen89 commented Mar 26, 2024

@alxhub

tl;dr: Shouldn't the docs say the same as the NG0950 error page is saying - that the input signals will be guaranteed available in the ngOnInit lifecycle and not before?

Here is a small stackblitz of the case: https://stackblitz.com/edit/stackblitz-starters-znzjc1?file=src%2Ffoo.component.ts

@Component({
  selector: 'app-foo',
  standalone: true,
  template: `
    <p>I am a {{ bar() }} foo! - and I am the opposite: {{ noBar() }}</p>
  `,
})
export class FooComponent implements OnInit {
  bar = input.required<boolean>();

  // this is working as expected
  noBar = computed(() => !this.bar());

  get currentBar() {
    try {
      return this.bar();
    } catch (err) {
      console.error('instant access not working');
    }
    return undefined;
  }

  // ! throws NG0950: Input is required but no value is available yet.
  barWithoutComputed = this.currentBar;

  constructor() {
    try {
      // ! throws NG0950: Input is required but no value is available yet.
      this.bar();
      console.log('constructor works');
    } catch (err) {
      console.error('constructor is not working');
    }

    afterRender(() => {
      // this is working
      this.bar();
      console.log('afterRender works');
    });
  }

  ngOnInit() {
    // this is working
    this.bar();
    console.log('ngOnInit works');
  }
}

After looking into this and the error-code NG0950 which says:

This can happen when a required input is accessed too early in your directive or component. This is commonly happening when the input is read as part of class construction.

Inputs are guaranteed to be available in the ngOnInit lifecycle hook and afterwards.

In the docs page for Signal Inputs it says the following which can be kind of misleading ( I get that the intention was another one, but the word always is too strong in here imho)

Required inputs always have a value of the given input type.

Or am I am on the wrong track here?

@JeanMeche
Copy link
Member

Angular can't bind an input while constructing the class. Anything that happens before the binding will throw NG950.
Your currentBar getter is run during class contruction, same as your bar() invokation in the constructor.

effect() is scheduled to run only after init and the computed is executed during CD (so after init).

@JeanMeche JeanMeche closed this as not planned Won't fix, can't repro, duplicate, stale Mar 26, 2024
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Apr 26, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime core: inputs / outputs cross-cutting: signals needs reproduction This issue needs a reproduction in order for the team to investigate further
Projects
None yet
Development

No branches or pull requests

4 participants