Last active
March 23, 2025 18:54
-
-
Save envynoiz/6212c6007fb474ee60b270897a36c496 to your computer and use it in GitHub Desktop.
MatFormField Readonly Directive
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; | |
import { afterRenderEffect, AfterViewInit, DestroyRef, Directive, inject, input } from '@angular/core'; | |
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; | |
import { MatFormField } from '@angular/material/form-field'; | |
import { MatInput } from '@angular/material/input'; | |
import { fromEvent, filter } from 'rxjs'; | |
@Directive({ | |
selector: 'mat-form-field[readonlyField]' | |
}) | |
export class ReadonlyFieldDirective implements AfterViewInit { | |
// By default enabled once the directive gets attached to the host element | |
// but can be handled externally using this input binding of course. | |
public isReadonly = input<boolean, BooleanInput>(true, { | |
alias: 'readonlyField', | |
transform: coerceBooleanProperty | |
}); | |
private readonly MATERIAL_DISABLED_CLASSNAME = 'mdc-text-field--disabled'; | |
private destroyRef = inject(DestroyRef); | |
private matFormField = inject(MatFormField); | |
private readonlyAfterRenderEffect = afterRenderEffect({ | |
write: () => { | |
const readonlyValue = this.isReadonly(); | |
const textFieldContainerElm = this.matFormField._textField.nativeElement; | |
textFieldContainerElm.classList.toggle(this.MATERIAL_DISABLED_CLASSNAME, readonlyValue); | |
this.materialInput.readonly = readonlyValue; | |
} | |
}); | |
public get materialInput(): MatInput { | |
return this.matFormField._control as MatInput; | |
} | |
public ngAfterViewInit(): void { | |
// Not a fancy way to get the native element from matInput instance :) | |
// --->> this.materialInput['_elementRef'].nativeElement | |
// Let's try to follow some "good practices" to avoid the previous literal accessor. | |
const inputElm = this.matFormField._textField.nativeElement.querySelector('[matInput]') as Element; | |
// Native HTML "readonly" state does not have the same behavior like the "disabled" one. | |
// Readonly by default preserves the focus behavior and I guess that's one of the reason why Material | |
// does not handle this. Yes this is intrusive but will avoid the focus in our matInput element. | |
fromEvent(inputElm, 'focusin') | |
.pipe( | |
filter(() => this.isReadonly()), | |
takeUntilDestroyed(this.destroyRef) | |
) | |
.subscribe(() => this.materialInput._focusChanged(false)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage examples: