Canonical: Marking @Input as required
🚀 feature request
Relevant Package
This feature request is for @angular/core
Description
There is a long history of discussion about whether / how to have the compiler enforce that specific inputs are provided to components: #11904 #18156 #24879 #28403 #30974
However, all of those are now closed and locked. They contain some advice about how to mitigate the problem, or kick the can down the road ("One day I hope we'll be able to do this. That day is not today."), but thanks to the lock-bot none of them can be used to continue the conversation, so I guess we need yet another issue, because I believe the problem still exists.
Describe the solution you'd like
It should be possible to annotate inputs such that using a component in a template without providing a value for that input is a compile-time error. Template checking has come a long way, and it's already possible for the compiler to flag providing the wrong type as an argument. Combine with the strictPropertyInitializers flag, and such a check could rely on the existing ! assertion operator.
Describe alternatives you've considered
The best approach I've seen while reviewing the above issues is specifying the required attributes in the selector component metadata, but a) apparently that won't catch passing null or undefined to an Input that not defined with a type that includes those values, and b) that appears to break certain test harnesses? Also, I haven't started using strictTemplates / fullTemplateTypeCheck yet due to some problems in previous versions with narrowing on *ngIf that may or may not have been resolved -- those existing capabilities may address some or all of this need.
Combine with the strictPropertyInitializers flag, and such a check could rely on the existing ! assertion operator.
For information, it is what Ionic's Stencil is doing.
apparently that won't catch passing null or undefined
This is already handled by strictTemplates angular compiler setting (in particular, strictNullInputTypes)
I'm wondering though if adding the input names to the directive selector is actually the official recommendation to achieve the goal - or rather a hackish workaround. If it is recommended - maybe it should be fixed as such in the docs?
I'm wondering though if adding the input names to the directive selector is actually the official recommendation to achieve the goal - or rather a hackish workaround. If it is recommended - maybe it should be fixed as such in the docs?
Even if you fix the docs, and start promoting this approach, I still think it's not ok.
First, the error will be wrong, as the compiler will complain about not finding the component, instead of the desired error.
Secondly, if you leave the selector, without the attribute, and use the component. You will end up in with a strange behavior. If you pass an undefined value, the compiler will complain, but if you skip the attribute, it will work, even though the runtime behavior is the same.
That's why I think, the best approach is to treat them the same, meaning that an error should occur, similar to passing an undefined value, as that's how the runtime behavior is.
Edit: The above example is reproducible in strict mode, I'm not sure if it works outside it.
Any news on that?
apparently that won't catch passing null or undefined
This is already handled by
strictTemplatesangular compiler setting (in particular,strictNullInputTypes)I'm wondering though if adding the input names to the directive selector is actually the official recommendation to achieve the goal - or rather a hackish workaround. If it is recommended - maybe it should be fixed as such in the docs?
I think I have a working solution https://github.com/angular/angular/issues/46949 - but my solution is with setters and it just got marked as a duplicate - since I'm not so familiar with the eco system of those things could someone please check my proposal I even linked the solution.
There is an alternative way to make simple inputs work by checking them onChanges manually.
The solution proposed in https://github.com/angular/angular/issues/46949 is a run-time check, which is already possible with custom code in ngOnInit. It just abstracts away some repeating boilerplate.
What we need though is a compile-time check. I sort of realize why it's hard to do given the existing angular components syntax and TS capabilities. Yet I don't lose hope that someone smarter than me comes up with a dev-friendly solution for it. A lack of such an approach makes null safety in an Angular app impossible. This fails to satisfy expectations (or I daresay, requirements) to modern code.
The solution proposed in #46949 is a run-time check, which is already possible with custom code in ngOnInit. It just abstracts away some repeating boilerplate.
It gets the job done and well, what do you want from a framework to write for every input every time check me onInit A,B and C? Also this isn't right at all it dosent work onInit (need to refactor the function names). It changes the value while making the metadata of the class and it overrides (in my current example) the setter so it functions like a guard - every time you change the input it checks the function which it overrided and then triggers the original function. There is a solution without setters by using onChanges - it would override the current onChanges where it would do the checks and then it would trigger the original onChanges. I can code it but I wanted to see how the feedback will be on this and its the worst case scenario.
What we need though is a compile-time check. I sort of realize why it's hard to do given the existing angular components syntax and TS capabilities.
Okay but this is pretty much impossible in .NET so why do you expect it to be possible in TypeScript / JavaScript? Imagine I get a library from someone else, the return type is number but at some point I get null / undefined or NaN. This is JavaScript and its duct typing - so why do we set such high standards? What is really the reason you don't accept my solution if its working and will work in 100% cases? Instead you make the point that you hope and think someone smarter will come - more on that below.
Yet I don't lose hope that someone smarter than me comes up with a dev-friendly solution for it. A lack of such an approach makes null safety in an Angular app impossible. This fails to satisfy expectations (or I daresay, requirements) to modern code.
This is the most disappointing part of your comment and when I read it it made me totally confused. I have so many thoughts on this but I'll just be streight and say that my solution is a working solution which I tested and I could not find anywhere such a solution.
My arguments:
- A working solution today is far better then maybe a hope tomorrow.
- This solution will work in 100% cases I don't know what hope you have for a typesafe solution but it won't work always.
- I can't believe I have to say this but having per input 10 lines of code less is a good thing.
- This would help to avoid a lot of bugs, everytime you get something which you don't expect you will see it as an error. As it should be from my point of view - we do the same in backend languages in every constructor we check if the value exists and if not we throw an error.
- This has been open for 2 years for such a simple feature do you want everyone now to wait 2 more years :)
- It tells you for which NgComponent and Input the error happened.
- Its probably possible to extend the current @Input and @Component decorator to work with this without breaking changes.
- My solution is also not a breaking change and its declerative per individual Input.
I kind of see where you are guys comming from but I think together we will be that 'smarter person' so my solution dose do a compile time change and then it executes the specific function for that specific input every time the input changes.
Also I'm open for discussions but you need to point out objective arguments I can't accept an argument to be 'hope'.
In .net you have constructors where you must specify all the parameters.
Btw it the same here - basically this fields must be treated as required constructor fields for control, represented by particular template usage
Hi @muhamedkarajic , I like the idea of extending @Input as a way of marking-as-required, but I think you may be confused about the intent of this issue (and the previous ones that I linked in my original write-up). Angular uses TypeScript, and the whole point of using TypeScript instead of vanilla JavaScript is to get compile-time, rather than run-time, error handling. Having to build and run your project before seeing the error is simply a worse developer experience. Your code is already capable of capturing whether a value can possibly be null or undefined, let's take advantage of that!
As I wrote previously, it's already possible to generate a compile-time error when a required input is omitted, it just happens to be a somewhat confusing error. When a previous comment referred to a "dev friendly" solution, I believe they meant that the ideal feature would generate an error message immediately in your IDE, that tells you exactly what you did wrong (i.e., omitted a required input).
Hi @muhamedkarajic , I like the idea of extending @Input as a way of marking-as-required, but I think you may be confused about the intent of this issue (and the previous ones that I linked in my original write-up).
My proposal was just linked as duplicate and now here I'm.
Angular uses TypeScript, and the whole point of using TypeScript instead of vanilla JavaScript is to get compile-time, rather than run-time, error handling. Having to build and run your project before seeing the error is simply a worse developer experience.
I understood that and I gave good examples why thats not going to work for a lot of cases.
Your code is already capable of capturing whether a value can possibly be null or undefined, let's take advantage of that!
Thank you but I don't know what to do from here on, unless someone explains it to me.
As I wrote previously, it's already possible to generate a compile-time error when a required input is omitted, it just happens to be a somewhat confusing error. When a previous comment referred to a "dev friendly" solution, I believe they meant that the ideal feature would generate an error message immediately in your IDE, that tells you exactly what you did wrong (i.e., omitted a required input).
I didn't catch the hole conversation cause I just got here because my proposal was marked as duplicate. Still I just don't see how that is supposed to be possible, my solution is more to make your life easier to catch bugs when you have edge cases, regarding the required input .NET has that as well but you still have the issue that the value can be null - the typescript compiler just can't fetch such a thing cause a type is one thing and a true compile time value is anothing thing.
In .net you have constructors where you must specify all the parameters.
Btw it the same here - basically this fields must be treated as required constructor fields for control, represented by particular template usage
Can't you do that already with '!' I think someone wrote that but thats still having that issue that in compile time something which is null can be still passed as input - same issue with .NET even if you expect an object your library can sent null / undefined and you then get an unexpected error or even wrost things seem to go fine but arent.
It looks like you maybe haven't spent a lot of time with Typescript, which might be the source of some of your confusion. In TS, null and undefined are their own types, and a property or variable can have a type of e.g. number without allowing null or undefined. In that case, any code that might assign those values will be flagged as an error at compile-time. That's what I meant when I said
Your code is already capable of capturing whether a value can possibly be null or undefined
If your code is properly typed, and does not work outside the type system (casting to any, using @ts-ignore, etc), you can be confident that the runtime value will definitely never be null or undefined. That's the whole point of the strictNullChecks flag.
The reason for this issue is that this safety can be extended to the template checker, to ensure that you never bind components in a way that will pass null or undefined to an input that expects never to get those values. We just want the template engine to properly preserve the (compile-time) safety guarantees that the type-checker already provides.
It looks like you maybe haven't spent a lot of time with Typescript, which might be the source of some of your confusion. In TS, null and undefined are their own types, and a property or variable can have a type of e.g. number without allowing null or undefined. In that case, any code that might assign those values will be flagged as an error at compile-time.
This is totally known to any developer who uses type saftly but it dosent work for third party APIs when they send something which you don't expect - sure you can define a interface which has a number which can't be null but you can't ensure it will be null at that moment which you are expecting (in the run time).
If your code is properly typed, and does not work outside the type system (casting to any, using @ts-ignore, etc), you can be confident that the runtime value will definitely never be null or undefined. That's the whole point of the strictNullChecks flag.
If we are talking about 'required' inputs I don't expect it to be typesafe only I expect it to be something which is required and the component won't work without providing it. Your solution would still mean the current Angular behavior is present and thats rendering the component even if the input is null - again those edge cases which you can't make typesafe.
For me required means runtime errors if the value isnt of the expected type.
What else would be possible with the runtime solution would be that you can define configuration per project what should happen if the result isnt the one which is expected, e.g. error, warrning, ignore setting the new input value. For the type-safty I just don't see the overall benefit for something more serious, the current Angular behavior seems fine to me, maybe not the best errors but that wasn't the reason I proposed the solution in the first place - my proposal was not regarding this one it was just marked as duplicated cause it refers to the same topic.
I see, you clearly want runtime checks, and you are correct that this issue focuses on the type checker instead. I would be surprised if the Angular team wants to implement runtime checks like you're suggesting - they typically have a performance cost - but you are welcome to ask them to reopen your issue.
I see, you clearly want runtime checks, and you are correct that this issue focuses on the type checker instead. I would be surprised if the Angular team wants to implement runtime checks like you're suggesting - they typically have a performance cost - but you are welcome to ask them to reopen your issue.
I have written in my proposal to reopen it they said they will take a look at every option - therefor I'm here. The performance issue isn't in my oppinion a cost cause you don't need to implement that decorator if you don't want to and if you do implement it - it just means you saved the boilerplate, it would function like a guard you have guards for routing etc why not having them for inputs.
I believe the runtime check is just a nice way to do what's already possible in ngOnInit or ngOnChanges with the ! operator. I believe having the ability to get an error on the component template declaration stating that input X is missing but required is a great benefit. The whole type system is meant for those checks in compile time vs runtime to catch bugs as early as possible.
Concerning the actual framework implementation - I am not sure the ! operator can be used to type check but that's of course the best solution. In case it's not possible - maybe a compile time known decorator like input will do the trick.
Only thing worth thinking about is missing vs undefined which I believe should be based on exactOptionalPropertyTypes - or if not possible a parameter to the mentioned required decorator.
...having the ability to get an error on the component template declaration stating that input X is missing but required is a great benefit.
^^
Only thing worth thinking about is missing vs undefined which I believe should be based on exactOptionalPropertyTypes - or if not possible a parameter to the mentioned required decorator.
Regarding the undefined vs missing that can be done by having a boolean which will be set to true if the setter is ever called (before onInit), then OnInit you can check all inputs were set.
Could anyone help me find the source code of the Angular Input Decorator? I found this link but I don't see the @Input decorator.
OK so again, this issue is not about runtime checks, it is about changing the behavior of the template engine. If you'd like to continue discussing runtime checks please do so in the issue you opened about those, to avoid confusing future visitors here. (The runtime-check conversation already represents more than half the posts in this thread!)
OK so again, this issue is not about runtime checks, it is about changing the behavior of the template engine. If you'd like to continue discussing runtime checks please do so in the issue you opened about those, to avoid confusing future visitors here. (The runtime-check conversation already represents more than half the posts in this thread!)
You can feel free to write in the closed issue your point of view: https://github.com/angular/angular/issues/46949.
I made my point clear and the message from the person who marked my post as duplicate was also clear. Dosen't also help that this is 2 years old and nothing moved.
I don't mind if no one accepts my solution but thats on the angular dev team to decide so please stop forcing me to open a new issue.
You can feel free to write in the closed issue your point of view
Done. (I didn't want you to open a new issue, I wanted to argue to reopen your closed issue, so I argued for that.)
Dosen't also help that this is 2 years old and nothing moved.
Actually, very nearly 6 years -- due to the lock-bot, this issue keeps getting opened, locked, then opened again. There's a list in the OP.
Done. (I didn't want you to open a new issue, I wanted to argue to reopen your closed issue, so I argued for that.)
I have tried that and as the other person said they are going to consider all possibilities.
What about @RequireBinding() decorator that would only enforce that the binding in template occurs, it can be bind to undefined, null, a function or a field or primitive - basicly whatever. All that it would be responsible would enforce that
@RequireBinding()
@Input() a: string | undefined | null;
<component-with-required-field-a></component-with-required-field-a> wold throw compile time error
<component-with-required-field-a [a]="undefined"></component-with-required-field-a> wold not
I propose @RequireBinding instead of @Required as it clearly states that it is binding that is required cause without it mayby component doesn't make sense - for example form component with [formControl] input.
Alternative would be adding a flag to @Input(name, {requireBinding: true}) or @Input({requireBinding: true})
It could probably even be used with outputs as well
@RequireBinding()
@Output() clicked = new EventEmmiter();
witch would indicate that we are expecting consumer to handle that event.
RequireBinding of course would not stop users from using compoennts dynamicly without providing any inputs as it would be strictly developer friendly addition for templating only.
What about
@RequireBinding()decorator that would only enforce that the binding in template occurs, it can be bind toundefined,null, a function or a field or primitive - basicly whatever. All that it would be responsible would enforce that@RequireBinding() @Input() a: string | undefined | null;
<component-with-required-field-a></component-with-required-field-a>wold throw compile time error<component-with-required-field-a [a]="undefined"></component-with-required-field-a>wold notI propose
@RequireBindinginstead of@Requiredas it clearly states that it is binding that is required cause without it mayby component doesn't make sense - for example form component with[formControl]input.Alternative would be adding a flag to
@Input(name, {requireBinding: true})or@Input({requireBinding: true})It could probably even be used with outputs as well
@RequireBinding() @Output() clicked = new EventEmmiter();witch would indicate that we are expecting consumer to handle that event.
RequireBindingof course would not stop users from using compoennts dynamicly without providing any inputs as it would be strictly developer friendly addition for templating only.
What makes it necessary to put it as 'RequireBinding' the input is supposed to be required? Binding would refear to HTML Binding...
Any progress with this issue? :(
Any progress with this issue? :(
I just wanted to ask the same :)
What exactly about Typescript makes this not feasible? This is a template error, and it already goes through a compilation step. Can the compiler not infer a type from the Class instance and use reflect-metadata to extract Inputs so that they can be marked as required in the extracted type unless they are optional? Or perhaps the language service can do the heavy lifting here?
A bit beyond my understanding as I'm not well versed in the compiler, but Vue 3 + Volar support required props through a combination of compilation + the language service, so it's clearly feasible.
Most (if not all?) of the typechecks in the templates are implemented as Type-check Blocks (TCBs) - specially crafted TS snippets that are then fed to the regular typescript compiler. What could a TCB checking for missing required inputs look like?
Most (if not all?) of the typechecks in the templates are implemented as Type-check Blocks (TCBs) - specially crafted TS snippets that are then fed to the regular typescript compiler. What could a TCB checking for missing required inputs look like?
I honestly had no idea how it was implemented...haven't had any luck tracking the source, so apologies if the suggestion was a bit ignorant. Would you have a link to the source handy? Haven't had much luck tracking down the template type checks.
Maybe Vue can get around it since it compiles the template down to a render function. Not sure what Angular's approach is.
@jmroon they covered the subject in the blog post: https://blog.angular.io/how-the-angular-compiler-works-42111f9d2549
My understanding is that the TS errors that TS compiler produces for those TCBs are surfaced directly to the user as a result. Now the problem is - what kind of a TCB should it be so that the TS-specific error would make sense and be clear to the end user?
Yeah, I can see how it's different in Vue. In Angular, a component is a class, and the compiled template is basically instantiating this class and assigns its input properties one by one.
However, perhaps it could be implemented as an extended diagnostic (introduced in Angular 13)?
What could a TCB checking for missing required inputs look like?
Naively I would assume given a component or directive, create an interface for that component containing only the required inputs (typed any), and try to assign an object with only the properties specified in the using template set to an arbitrary value, so that TS2741 ("Property x is missing") is raised. I.e.,
@Component({selector: 'app-hero', template: '...'})
export class HeroComponent {
@Required() @Input()
heroes: Hero[];
@Input()
label: string;
}
@Component({selector: 'app-hero-user', template: '<app-hero [label]="label"></app-hero>'})
export class HeroUserComponent {...}
would become a type check
interface HeroComponent_req {
heroes: any;
label?: any;
}
const _heroUserComponentRequired: HeroComponent_req = {label: null}; // error: "TS2741: Property 'heroes' is missing in type '{ label: null; }' but required in type 'HeroComponent_req'."
However, is a type check even needed? For validating required inputs are present all that needs to be known is if they are "mentioned" on the use site. So the logic becomes:
- Enumerate a component's or directive's required inputs.
- Enumerate the bound inputs or attributes at the use site (regardless of their value, because typechecking is another step).
- Raise an error if 1 is not a subset of 2.
Is that not feasible with ngc?