fix(core): control not recognized when input has directive injecting ViewContainerRef (#64368)

When a directive injects a `ViewContainerRef`, the runtime inserts a container that was throwing off the logic that recognizes native controls.

These changes switch to check if the node is a native control through the `TNode`. This also makes it a bit less prone to breaking during SSR.

Fixes #64362.

PR Close #64368
This commit is contained in:
Kristiyan Kostadinov
2025-10-13 18:12:42 -04:00
committed by Andrew Kushnir
parent 356dc4c952
commit ab415f3d7f
2 changed files with 75 additions and 25 deletions
@@ -8,7 +8,9 @@
import {
Component,
Directive,
ElementRef,
inject,
Injector,
input,
inputBinding,
@@ -17,6 +19,7 @@ import {
signal,
viewChild,
viewChildren,
ViewContainerRef,
} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {
@@ -1079,6 +1082,39 @@ describe('field directive', () => {
).toEqual(['manual disabled', 'schema disabled']);
});
it('should bind to native control that has directive injecting ViewContainerRef', () => {
@Directive({selector: 'input'})
class InputDirective {
vcr = inject(ViewContainerRef);
}
@Component({
imports: [Field, InputDirective],
template: `<input [field]="f">`,
})
class TestCmp {
f = form(signal('test'));
}
const fix = act(() => TestBed.createComponent(TestCmp));
const input = fix.nativeElement.firstChild as HTMLInputElement;
const cmp = fix.componentInstance as TestCmp;
// Initial state
expect(input.value).toBe('test');
// Model -> View
act(() => cmp.f().value.set('testing'));
expect(input.value).toBe('testing');
// View -> Model
act(() => {
input.value = 'typing';
input.dispatchEvent(new Event('input'));
});
expect(cmp.f().value()).toBe('typing');
});
describe('should work with different input types', () => {
it('should sync string field with number type input', () => {
@Component({