Skip to content

fix(core): only set input on specific instance in ComponentRef.setInput - #46997

Closed
crisbeto wants to merge 1 commit into
angular:mainfrom
crisbeto:set-input-instance
Closed

crisbeto wants to merge 1 commit into
angular:mainfrom
crisbeto:set-input-instance

Conversation

@crisbeto

Copy link
Copy Markdown
Member

ComponentRef.setInput works by calling setInputsForProperty internally. It can be problematic, because it will set the all inputs on the specific node matching the passed-in name, including other directives that aren't associated with the ComponentRef. Currently this isn't a problem, because the only way to get a ComponentRef is as a result of ComponentFactory.create and there is no way to apply other directives through there, however I see a couple of ways that it can manifest itself in the future:

  1. Once the runtime for host directives is implemented (after refactor(compiler): add support for host directives #46868), users will be able to apply directives to the root component.
  2. If we were to make it possible to inject ComponentRef in the future.

These changes add some extra logic so that the input is only assigned to the component instance that is associated with the ComponentRef.

`ComponentRef.setInput` works by calling `setInputsForProperty` internally. It can be problematic, because it will set the all inputs on the specific node matching the passed-in name, including other directives that aren't associated with the `ComponentRef`. Currently this isn't a problem, because the only way to get a `ComponentRef` is as a result of `ComponentFactory.create` and there is no way to apply other directives through there, however I see a couple of ways that it can manifest itself in the future:
1. Once the runtime for host directives is implemented (after angular#46868), users will be able to apply directives to the root component.
2. If we were to make it possible to inject `ComponentRef` in the future.

These changes add some extra logic so that the input is only assigned to the component instance that is associated with the `ComponentRef`.
@crisbeto
crisbeto force-pushed the set-input-instance branch from 72ef687 to 5e2a32a Compare July 31, 2022 11:16
if (this._rootLView[index] === this.instance) {
setInput(lView[TVIEW], this.instance, index, name, dataValue[i + 1] as string, value);
markDirtyIfOnPush(lView, this._tNode.index);
break;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This break may be problematic if we somehow end up with multiple inputs on the same instance with the same name. Not sure if that's actually possible, but I'm open to removing it.

reportUnknownPropertyError(message);
for (let i = 0; i < dataValue.length; i += 2) {
const index = dataValue[i] as number;
if (this._rootLView[index] === this.instance) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Figuring out which inputs belong to this instance like this felt a little weird, but the only alternative would be to look at the index and I couldn't find a good way of getting the index of the component instance within the root LView.

* renderComponent() and ViewContainerRef.createComponent().
*/
export function createRootComponent<T>(
function createRootComponent<T>(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated change to the rest of the fix, I just saw that this function is only used within the file.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could we move those refactor changes to a separate (refactor) commit?

for (let i = 0; i < inputs.length;) {
const index = inputs[i++] as number;
const privateName = inputs[i++] as string;
for (let i = 0; i < inputs.length; i += 2) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is also unrelated to the fix, but I think that it's more readable like this compared to incrementing i from within the loop. I'm fine with reverting it as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could we move those refactor changes to a separate (refactor) commit?

@crisbeto crisbeto added action: review The PR is still awaiting reviews from at least one requested reviewer area: core Issues related to the framework runtime target: patch This PR is targeted for the next patch release labels Jul 31, 2022
@ngbot ngbot Bot modified the milestone: Backlog Jul 31, 2022
@crisbeto
crisbeto marked this pull request as ready for review July 31, 2022 12:16

@pkozlowski-opensource pkozlowski-opensource left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@crisbeto there are few things here:

  • the current behaviour of the setInput assumes that it behaves as a regular binding in a template and this is intentional. IMO we we should define the semantics of the bindings in a template to inputs for components with host directives first and use the same behaviour; in this sense I would rather do the runtime part of host directives first, iron out the semantics and modify the ComponentRef bit then - if needed;

  • the assumptions in this PR is that, when binding to a component's inputs, its host directives shouldn't get the binding. This would be different from how the runtime behaves today for directives composed on the same host node. I was under the impression that host directives with exposed inputs should behave as a set of directives on the same node;

  • currently there is no way of writing tests for this change;

I would much rather do the change in the runtime that accounts for the host directives and IMO the setInput should "just work".

So my preference would be to do the setInput change after the regular bindings are figured out.

@crisbeto

crisbeto commented Aug 1, 2022

Copy link
Copy Markdown
Member Author

You're right that the intent with host directives is for them to work as if they were applied inside a template. I think that this case is different, because you have a reference pointing to a specific component class. As a user, I would find it odd if calling setInput would modify the input of a host directive I have no access to.

Small aside regarding tests: I have it on my list to add tests for this specific use case once I start working on the runtime implementation.

@pkozlowski-opensource

Copy link
Copy Markdown
Member

You're right that the intent with host directives is for them to work as if they were applied inside a template. I think that this case is different, because you have a reference pointing to a specific component class. As a user, I would find it odd if calling setInput would modify the input of a host directive I have no access to.

@crisbeto I can see it as a different way of looking at the problem. Again, the initial assumption was that it does behave as a regular input. And it does make total sense to me in case of host directives. We can certainly review this assumption. Maybe an item on the fwk sync agenda?

@crisbeto

crisbeto commented Aug 1, 2022

Copy link
Copy Markdown
Member Author

Sure, let's discuss it. I've added it to the agenda.

@crisbeto crisbeto removed the action: review The PR is still awaiting reviews from at least one requested reviewer label Aug 16, 2022
@crisbeto crisbeto self-assigned this Aug 16, 2022
@crisbeto
crisbeto marked this pull request as draft August 16, 2022 05:12
@crisbeto crisbeto closed this Sep 20, 2022
@angular-automatic-lock-bot

Copy link
Copy Markdown

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 Oct 21, 2022
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 target: patch This PR is targeted for the next patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants