Skip to content

Commit 9de1e9d

Browse files
crisbetoAndrewKushnir
authored andcommitted
fix(compiler): incorrectly matching directives on attribute bindings (#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
1 parent d980af6 commit 9de1e9d

File tree

3 files changed

+50
-2
lines changed

3 files changed

+50
-2
lines changed

packages/compiler-cli/src/ngtsc/typecheck/test/diagnostics_spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,31 @@ class TestComponent {
10931093
`TestComponent.html(1, 1): Required input 'customAlias' from directive HostDir must be specified.`
10941094
]);
10951095
});
1096+
1097+
it('should not report missing required inputs for an attribute binding with the same name',
1098+
() => {
1099+
const messages = diagnose(
1100+
`<div [attr.maxlength]="123"></div>`, `
1101+
class MaxLengthValidator {
1102+
maxlength: string;
1103+
}
1104+
class TestComponent {}
1105+
`,
1106+
[{
1107+
type: 'directive',
1108+
name: 'MaxLengthValidator',
1109+
selector: '[maxlength]',
1110+
inputs: {
1111+
maxlength: {
1112+
classPropertyName: 'maxlength',
1113+
bindingPropertyName: 'maxlength',
1114+
required: true
1115+
},
1116+
},
1117+
}]);
1118+
1119+
expect(messages).toEqual([]);
1120+
});
10961121
});
10971122

10981123
// https://github.com/angular/angular/issues/43970

packages/compiler/src/render3/view/util.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {ConstantPool} from '../../constant_pool';
10-
import {Interpolation} from '../../expression_parser/ast';
10+
import {BindingType, Interpolation} from '../../expression_parser/ast';
1111
import * as o from '../../output/output_ast';
1212
import {ParseSourceSpan} from '../../parse_util';
1313
import * as t from '../r3_ast';
@@ -279,7 +279,9 @@ export function getAttrsForDirectiveMatching(elOrTpl: t.Element|
279279
});
280280

281281
elOrTpl.inputs.forEach(i => {
282-
attributesMap[i.name] = '';
282+
if (i.type === BindingType.Property) {
283+
attributesMap[i.name] = '';
284+
}
283285
});
284286
elOrTpl.outputs.forEach(o => {
285287
attributesMap[o.name] = '';

packages/compiler/test/render3/view/binding_spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ function makeSelectorMatcher(): SelectorMatcher<DirectiveMeta[]> {
7272
selector: '[hasInput]',
7373
animationTriggerNames: null,
7474
}]);
75+
matcher.addSelectables(CssSelector.parse('[sameSelectorAsInput]'), [{
76+
name: 'SameSelectorAsInput',
77+
exportAs: null,
78+
inputs: new IdentityInputMapping(['sameSelectorAsInput']),
79+
outputs: new IdentityInputMapping([]),
80+
isComponent: false,
81+
isStructural: false,
82+
selector: '[sameSelectorAsInput]',
83+
animationTriggerNames: null,
84+
}]);
7585
return matcher;
7686
}
7787

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

189+
it('should not match directives on attribute bindings with the same name as an input', () => {
190+
const template =
191+
parseTemplate('<ng-template [attr.sameSelectorAsInput]="123"></ng-template>', '', {});
192+
const binder = new R3TargetBinder(makeSelectorMatcher());
193+
const res = binder.bind({template: template.nodes});
194+
const el = template.nodes[0] as a.Element;
195+
const input = el.inputs[0];
196+
const consumer = res.getConsumerOfBinding(input);
197+
expect(consumer).toEqual(el);
198+
});
199+
179200
it('should bind to the encompassing node when no directive input is matched', () => {
180201
const template = parseTemplate('<span dir></span>', '', {});
181202
const binder = new R3TargetBinder(makeSelectorMatcher());

0 commit comments

Comments
 (0)