Skip to content

Commit

Permalink
fix(compiler): incorrectly matching directives on attribute bindings (#…
Browse files Browse the repository at this point in the history
…49713)

Fixes that the compiler was matching directives based on `attr` bindings which doesn't correspond to the runtime behavior. This wasn't a problem until now because the matched directives would basically be a noop, but they can cause issues with required inputs.

PR Close #49713
  • Loading branch information
crisbeto authored and AndrewKushnir committed Apr 11, 2023
1 parent 4e41d12 commit 8020347
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 2 deletions.
25 changes: 25 additions & 0 deletions packages/compiler-cli/src/ngtsc/typecheck/test/diagnostics_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,31 @@ class TestComponent {
`TestComponent.html(1, 1): Required input 'customAlias' from directive HostDir must be specified.`
]);
});

it('should not report missing required inputs for an attribute binding with the same name',
() => {
const messages = diagnose(
`<div [attr.maxlength]="123"></div>`, `
class MaxLengthValidator {
maxlength: string;
}
class TestComponent {}
`,
[{
type: 'directive',
name: 'MaxLengthValidator',
selector: '[maxlength]',
inputs: {
maxlength: {
classPropertyName: 'maxlength',
bindingPropertyName: 'maxlength',
required: true
},
},
}]);

expect(messages).toEqual([]);
});
});

// https://github.com/angular/angular/issues/43970
Expand Down
6 changes: 4 additions & 2 deletions packages/compiler/src/render3/view/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import {ConstantPool} from '../../constant_pool';
import {Interpolation} from '../../expression_parser/ast';
import {BindingType, Interpolation} from '../../expression_parser/ast';
import * as o from '../../output/output_ast';
import {ParseSourceSpan} from '../../parse_util';
import * as t from '../r3_ast';
Expand Down Expand Up @@ -279,7 +279,9 @@ export function getAttrsForDirectiveMatching(elOrTpl: t.Element|
});

elOrTpl.inputs.forEach(i => {
attributesMap[i.name] = '';
if (i.type === BindingType.Property) {
attributesMap[i.name] = '';
}
});
elOrTpl.outputs.forEach(o => {
attributesMap[o.name] = '';
Expand Down
21 changes: 21 additions & 0 deletions packages/compiler/test/render3/view/binding_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ function makeSelectorMatcher(): SelectorMatcher<DirectiveMeta[]> {
selector: '[hasInput]',
animationTriggerNames: null,
}]);
matcher.addSelectables(CssSelector.parse('[sameSelectorAsInput]'), [{
name: 'SameSelectorAsInput',
exportAs: null,
inputs: new IdentityInputMapping(['sameSelectorAsInput']),
outputs: new IdentityInputMapping([]),
isComponent: false,
isStructural: false,
selector: '[sameSelectorAsInput]',
animationTriggerNames: null,
}]);
return matcher;
}

Expand Down Expand Up @@ -176,6 +186,17 @@ describe('t2 binding', () => {
expect(consumer.name).toBe('HasInput');
});

it('should not match directives on attribute bindings with the same name as an input', () => {
const template =
parseTemplate('<ng-template [attr.sameSelectorAsInput]="123"></ng-template>', '', {});
const binder = new R3TargetBinder(makeSelectorMatcher());
const res = binder.bind({template: template.nodes});
const el = template.nodes[0] as a.Element;
const input = el.inputs[0];
const consumer = res.getConsumerOfBinding(input);
expect(consumer).toEqual(el);
});

it('should bind to the encompassing node when no directive input is matched', () => {
const template = parseTemplate('<span dir></span>', '', {});
const binder = new R3TargetBinder(makeSelectorMatcher());
Expand Down

0 comments on commit 8020347

Please sign in to comment.