Question
How Angular Material mat-form-field Works with MatFormFieldControl
Question
In Angular Material, I am trying to create reusable wrapper components around form controls.
I created a custom field component that wraps a Material mat-form-field and uses content projection:
<mat-form-field>
<ng-content></ng-content>
<mat-hint align="start"><strong>{{ hint }}</strong></mat-hint>
<mat-hint align="end">{{ message.value.length }} / 256</mat-hint>
<mat-error>This field is required</mat-error>
</mat-form-field>
Then I created a textbox component that projects an Angular Material input into that wrapper:
<field hint="hint">
<input
matInput
[placeholder]="placeholder"
[value]="value"
(change)="onChange($event)"
(keydown)="onKeydown($event)"
(keyup)="onKeyup($event)"
(keypress)="onKeypress($event)"
/>
</field>
And I use it like this:
<textbox value="test" hint="my hint"></textbox>
At runtime, Angular Material logs this error:
mat-form-field must contain a MatFormFieldControl
It seems like the mat-form-field does contain the input[matInput], but only through ng-content projection inside another component.
Why does this error happen, and what is the correct way to build reusable Angular Material form-field wrapper components?
Short Answer
By the end of this page, you will understand why mat-form-field requires a direct Angular Material form control, how Angular content projection affects component queries, and how to build reusable wrapper components correctly in Angular Material.
Concept
Angular Material's mat-form-field is not just a visual container. It actively looks for a child control that implements the MatFormFieldControl contract.
Examples of controls that work with mat-form-field include:
inputwithmatInputtextareawithmatInputmat-select- custom components that explicitly implement
MatFormFieldControl
The key detail is that mat-form-field does not simply search the final rendered DOM in a generic way. It uses Angular's component and directive querying rules to find a compatible control in the expected view/content relationship.
When you wrap things like this:
<textbox>
<field>
<mat-form-field>
<ng-content></ng-content>
</mat-form-field>
</field>
</>
Mental Model
Think of mat-form-field like a power strip that only works if it can recognize the plug type.
mat-form-field= the power stripmatInput/mat-select/ customMatFormFieldControl= approved plug types- content projection = passing a plug through a wall opening
Even if you can visually see the plug near the power strip, the strip only works if Angular Material can detect that the plug is the right kind and is connected in the expected place.
So this is not about what the HTML looks like in DevTools. It is about what Angular sees in terms of component ownership and projected content.
Syntax and Examples
Core idea
A working Angular Material form field usually looks like this:
<mat-form-field>
<input matInput placeholder="Name" />
</mat-form-field>
Here, mat-form-field can directly find the input with the matInput directive.
A reusable component that works
Instead of projecting the input through another wrapper, place the mat-form-field and the control in the same template:
<mat-form-field>
<input
matInput
[placeholder]="placeholder"
[value]="value"
(input)="onInput($event)"
/>
<mat-hint align="start">{{ hint }}</mat-hint>
<mat-hint align=>{{ value?.length || 0 }} / 256
This field is required
Step by Step Execution
Consider this simplified version:
<!-- field.component.html -->
<mat-form-field>
<ng-content></ng-content>
</mat-form-field>
<!-- textbox.component.html -->
<field>
<input matInput />
</field>
What happens step by step
- Angular creates the
textboxcomponent. - Inside
textbox, Angular sees afieldcomponent. - The
input matInputbelongs to thetextboxtemplate, not thefieldtemplate. - The
fieldcomponent renders its own template, which containsmat-form-fieldand<ng-content>. - Angular projects the
input matInputinto the component's content slot.
Real World Use Cases
Reusable input components in design systems
Companies often build shared UI components such as:
- text boxes
- email fields
- password fields
- search boxes
- number inputs
These components usually need labels, hints, validation messages, and consistent styling. Angular Material supports this well, but the control must be structured so mat-form-field can detect it.
Form validation
A common use case is showing validation messages:
<mat-form-field>
<input matInput [formControl]="emailControl" placeholder="Email" />
<mat-error *ngIf="emailControl.hasError('required')">
Email is required
</mat-error>
</mat-form-field>
Shared form components across an app
Teams often create components such as:
app-textboxapp-password-fieldapp-date-fieldapp-select-field
Real Codebase Usage
In real Angular codebases, developers typically use one of these patterns.
1. Component owns the Material control
This is the most common approach.
<mat-form-field>
<input matInput [formControl]="control" />
<mat-error *ngIf="control.invalid">Invalid value</mat-error>
</mat-form-field>
Why it is common:
- simple to understand
- works reliably with Angular Material
- easy to test
- easy to connect to reactive forms
2. Use ControlValueAccessor for reusable inputs
If you want a reusable custom input component that works with Angular forms, implement ControlValueAccessor.
This lets your component behave like a normal form control.
Typical pattern:
- custom component owns its internal input
- component exposes value through
ControlValueAccessor - parent form uses
formControlNameorngModel
Common Mistakes
1. Assuming rendered DOM is all that matters
A common mistake is thinking:
If the
inputappears insidemat-form-fieldin the browser, Angular Material should accept it.
That is not always true. Angular cares about component templates, directives, and queries, not only the final DOM structure.
2. Wrapping mat-form-field and the control in separate components
Broken pattern:
<!-- field.component.html -->
<mat-form-field>
<ng-content></ng-content>
</mat-form-field>
<!-- textbox.component.html -->
<field>
<input matInput />
</field>
This can cause the MatFormFieldControl detection to fail.
3. Using [value] without proper form integration
This is often too limited:
Comparisons
| Approach | Works with mat-form-field easily? | Best for | Complexity |
|---|---|---|---|
Plain mat-form-field + input matInput in same template | Yes | Simple inputs | Low |
| Wrapper component that owns both field and input | Yes | Reusable design-system inputs | Low to medium |
| Wrapper component with projected input from another wrapper | Often problematic | Advanced composition | Medium |
Custom component implementing ControlValueAccessor | Yes, for Angular forms | Reusable form components | Medium |
Custom component implementing MatFormFieldControl |
Cheat Sheet
Quick rules
mat-form-fieldmust contain a control Angular Material recognizes.- Valid controls include
input[matInput],textarea[matInput],mat-select, and customMatFormFieldControlimplementations. - Do not assume final DOM structure is enough.
- Prefer putting
mat-form-fieldand its control in the same component template. - For reusable custom fields, use
ControlValueAccessor. - For custom Material-compatible controls, implement
MatFormFieldControl.
Safe pattern
<mat-form-field>
<input matInput [formControl]="control" />
<mat-error *ngIf="control.invalid">Invalid</mat-error>
</mat-form-field>
Risky pattern
<!-- Wrapper A -->
FAQ
Why does Angular Material say mat-form-field must contain a MatFormFieldControl?
Because mat-form-field could not find a compatible child control such as input[matInput], textarea[matInput], mat-select, or a custom MatFormFieldControl.
Why does it fail even though the input appears inside the rendered HTML?
Because Angular Material uses Angular component/directive queries, not just the final browser DOM structure.
Can I wrap mat-form-field in a reusable component?
Yes, but the safest approach is for that reusable component to also own the actual Material control in the same template.
Should I use ng-content with Angular Material form fields?
You can in some cases, but extra wrapper layers often cause detection problems. Test carefully.
What if I want my own custom input component to work inside mat-form-field?
Implement MatFormFieldControl. If it also needs to work with Angular forms, implement ControlValueAccessor too.
Is ControlValueAccessor enough by itself?
Mini Project
Description
Build a reusable Angular Material text input component for a company design system. The component should display a hint, a character count, and validation feedback without splitting the mat-form-field and the matInput across different wrapper components.
Goal
Create a reusable app-textbox component that works cleanly with Angular Material and Angular forms.
Requirements
[
"Create a textbox component that owns both mat-form-field and input matInput in the same template.",
"Accept placeholder, hint, and maxLength as inputs.",
"Display a live character count.",
"Use a FormControl to manage the value.",
"Show an error message when the field is required and empty."
]
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.
Angular formControl Error with Material Autocomplete: Why It Happens and How to Fix It
Learn why Angular says it cannot bind to formControl and how to fix Reactive Forms setup with Angular Material Autocomplete.