From cbfae68b5168beee31e2038d44705289f9cf70a7 Mon Sep 17 00:00:00 2001 From: Yanek Voloshchuk Date: Fri, 28 Mar 2025 16:33:21 +0100 Subject: [PATCH 1/9] readonly form-field and wrapper lifecycle approach refactored --- ...readonly-form-field-wrapper.component.html | 1 - .../readonly-form-field-wrapper.component.ts | 52 ++++++----- .../readonly-form-field.component.ts | 90 ++++++++----------- 3 files changed, 61 insertions(+), 82 deletions(-) diff --git a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.html b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.html index fb279c6c..31035d5d 100644 --- a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.html +++ b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.html @@ -10,7 +10,6 @@ [textAlign]="textAlign" [formatNumber]="formatNumber" [decimalPlaces]="decimalPlaces" - [roundDisplayValue]="roundValue" [autofillDecimals]="autofillDecimals" [unit]="unit" [unitPosition]="unitPosition" diff --git a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts index c3555802..9d2c30a8 100644 --- a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts +++ b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts @@ -1,13 +1,12 @@ import { - AfterViewChecked, AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnChanges, - OnInit, Output, SimpleChanges, ViewChild, @@ -28,12 +27,13 @@ import { ObserversModule } from '@angular/cdk/observers'; styleUrls: ['./readonly-form-field-wrapper.component.css'], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [NgIf, ReadOnlyFormFieldComponent, ObserversModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ReadOnlyFormFieldWrapperComponent implements OnInit, AfterViewInit, OnChanges, AfterViewChecked { +export class ReadOnlyFormFieldWrapperComponent implements AfterViewInit, OnChanges { @ViewChild('contentWrapper', { static: false }) - originalContent: ElementRef; + originalContent!: ElementRef; @ViewChild('readOnlyContentWrapper', { static: false }) - readOnlyContentWrapper: ElementRef; + readOnlyContentWrapper!: ElementRef; /** * If set to "false", the contained mat-form-field is rendered in all it's glory. @@ -55,7 +55,7 @@ export class ReadOnlyFormFieldWrapperComponent implements OnInit, AfterViewInit, @Input('unit') unit: string | null = null; @Input('unitPosition') unitPosition: 'right' | 'left' = 'left'; @Input('errorMessage') errorMessage: string | null = null; - @Input() id: string; + @Input() id!: string; /** * If set to "false", a readonly input will be rendered. * If set to "true", a readonly textarea will be rendered instead. @@ -65,7 +65,7 @@ export class ReadOnlyFormFieldWrapperComponent implements OnInit, AfterViewInit, /** * Defines the rows for the readonly textarea. */ - @Input() rows: number; + @Input() rows!: number; /** * If shrinkIfEmpty is set to "false", nothing changes @@ -78,11 +78,11 @@ export class ReadOnlyFormFieldWrapperComponent implements OnInit, AfterViewInit, /** * suffix iocon */ - @Input() suffix: string; + @Input() suffix!: string; /** * prefix iocon */ - @Input() prefix: string; + @Input() prefix!: string; /** * if cdkTextareaAutosize is active for textareas */ @@ -93,28 +93,27 @@ export class ReadOnlyFormFieldWrapperComponent implements OnInit, AfterViewInit, /** * Automatically taken from the contained */ - label: string; + label!: string; + private initialized = false; constructor( private changeDetector: ChangeDetectorRef, private rootFormGroup: FormGroupDirective, ) {} - ngOnInit(): void { - this.doRendering(); + ngOnChanges(_: SimpleChanges): void { + if (this.initialized) { + this.syncView(); + } } ngAfterViewInit(): void { - this.doRendering(); + this.initialized = true; + this.syncView(); + this.extractLabel(); this.extractValue(); } - ngAfterViewChecked(): void {} - - ngOnChanges(_: SimpleChanges): void { - this.doRendering(); - } - getLabel(): string { if (!this.label) { this.extractLabel(); @@ -135,21 +134,20 @@ export class ReadOnlyFormFieldWrapperComponent implements OnInit, AfterViewInit, this.extractValue(); } - private doRendering(): void { + private syncView(): void { if (!this.originalContent) { return; } if (!this.readonly) { - this.correctWidth(); - return; + this.applyFullWidthStyle(); + } else { + this.changeDetector.detectChanges(); } - - this.changeDetector.detectChanges(); } private extractLabel(): void { if (!this.originalContent || !this.originalContent.nativeElement) { - return null; + return; } const labelElement = this.originalContent.nativeElement.querySelector('mat-label'); this.label = labelElement ? labelElement.innerHTML : 'mat-label is missing!'; @@ -172,11 +170,11 @@ export class ReadOnlyFormFieldWrapperComponent implements OnInit, AfterViewInit, return; } if (form && form.get(formControlName)) { - this.value = form.get(formControlName).getRawValue(); + this.value = form.get(formControlName)?.getRawValue(); } } - private correctWidth(): void { + private applyFullWidthStyle(): void { const formField = this.originalContent.nativeElement.querySelector('mat-form-field'); if (formField) { formField.setAttribute('style', 'width:100%'); diff --git a/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.ts b/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.ts index ace1f5e9..cfe7ccc5 100644 --- a/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.ts +++ b/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.ts @@ -1,5 +1,5 @@ import { - AfterViewChecked, + AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -32,55 +32,41 @@ import { MatFormFieldModule } from '@angular/material/form-field'; templateUrl: './readonly-form-field.component.html', styleUrls: ['./readonly-form-field.component.css'], imports: [MatFormFieldModule, NgIf, MatInputModule, FormsModule, NgStyle, NgClass, MatTooltipModule, TextFieldModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ReadOnlyFormFieldComponent implements OnChanges, AfterViewChecked { - @ViewChild('contentWrapper', { static: false }) - originalContent: ElementRef; - @Input('useProjectedContent') useProjectedContent: boolean = false; - @Input('value') value?: any; - @Input('label') label: string; - @Input('textAlign') textAlign: 'right' | 'left' = 'left'; - @Input('formatNumber') formatNumber = false; - @Input('decimalPlaces') decimalPlaces = 2; - @Input('roundDisplayValue') roundValue = false; - @Input('autofillDecimals') autofillDecimals = false; - @Input('unit') unit: string | null = null; - @Input('unitPosition') unitPosition: 'right' | 'left' = 'left'; - @Input('errorMessage') errorMessage: string | null = null; +export class ReadOnlyFormFieldComponent implements OnChanges, AfterViewInit { + @Input() useProjectedContent: boolean = false; + @Input() value?: any; + @Input() label!: string; + @Input() textAlign: 'right' | 'left' = 'left'; + @Input() formatNumber = false; + @Input() decimalPlaces = 2; + @Input() autofillDecimals = false; + @Input() unit: string | null = null; + @Input() unitPosition: 'right' | 'left' = 'left'; + @Input() errorMessage: string | null = null; @Input() multiline = false; - @Input() rows: number; - @Input() id: string; - /* - * If shrinkIfEmpty is set to "false", nothing changes - * If set to "true" and multiline is also "true", the textarea will - * shrink to one row, if value is empty/null/undefined. - * Otherwise, the defined rows-value will be used - */ + @Input() rows!: number; @Input() shrinkIfEmpty = false; - /** - * suffix iocon - */ - @Input() suffix: string; - /** - * prefix iocon - */ - @Input() prefix: string; - /** - * if cdkTextareaAutosize is active for textareas - */ + @Input() suffix!: string; + @Input() prefix!: string; @Input() multilineAutoSize = false; + @Input() id!: string; + @Output() suffixClickedEmitter = new EventEmitter(); @Output() prefixClickedEmitter = new EventEmitter(); - @ViewChild('inputEl') inputEl: ElementRef; + @ViewChild('inputEl') inputEl!: ElementRef; + errorMatcher: ErrorStateMatcher = { isErrorState: () => !!this.errorMessage, }; - private unitSpan: HTMLSpanElement; - private textSpan: HTMLSpanElement; + private unitSpan!: HTMLSpanElement; + private textSpan!: HTMLSpanElement; + private viewInitialized = false; toolTipForInputEnabled = false; - toolTipText: string; + toolTipText!: string; constructor( private changeDetector: ChangeDetectorRef, @@ -102,13 +88,16 @@ export class ReadOnlyFormFieldComponent implements OnChanges, AfterViewChecked { autofillDecimals: this.autofillDecimals, }); } - this.changeDetector.detectChanges(); } - // TODO direct copy from NumericFieldDirective - ngAfterViewChecked(): void { + ngAfterViewInit(): void { + if (this.viewInitialized) { + return; + } + + this.viewInitialized = true; this.injectUnitSymbol(); - // If useProjectedContent is set to true, the input wont be show + // If useProjectedContent is set to true, the input not be show if (!this.useProjectedContent) { this.setReadonlyFieldStyle(); this.setTooltipForOverflownField(); @@ -125,7 +114,7 @@ export class ReadOnlyFormFieldComponent implements OnChanges, AfterViewChecked { private injectUnitSymbol(): void { // Need to inject the unit symbol when the input element width is set to its actual value, - // otherwise the icon wont show in the correct position + // otherwise the icon not show in the correct position if (!!this.unit && !this.unitSpan && this.inputEl.nativeElement.offsetWidth !== 0) { // Get the input wrapper and apply necessary styles const inputWrapper = this.inputEl.nativeElement.parentNode.parentNode; @@ -186,13 +175,9 @@ export class ReadOnlyFormFieldComponent implements OnChanges, AfterViewChecked { // Ellipsis is enabled by default as text-overflow behaviour private getTextOverFlowStyleValue(): string { // it works only if the style is added to the component directly. Should find a way for get it from the calculated - // style. Than it would be possible to define the text-overflow in css for the whole application + // style. Then it would be possible to define the text-overflow in css for the whole application const textOverflow = this.elementRef?.nativeElement?.style.textOverflow; - if (!textOverflow) { - return 'ellipsis'; - } - - return textOverflow; + return textOverflow || 'ellipsis'; } private setTooltipForOverflownField(): void { @@ -212,10 +197,7 @@ export class ReadOnlyFormFieldComponent implements OnChanges, AfterViewChecked { } private isTextOverflown(input: any): boolean { - if (input) { - return input.offsetWidth < input.scrollWidth; - } - return false; + return input && input.offsetWidth < input.scrollWidth; } private calculateToolTipText(): string { @@ -223,6 +205,6 @@ export class ReadOnlyFormFieldComponent implements OnChanges, AfterViewChecked { return this.value; } - return this.unitPosition === 'left' ? this.unit + ' ' + this.value : this.value + ' ' + this.unit; + return this.unitPosition === 'left' ? `${this.unit} ${this.value}` : `${this.value} ${this.unit}`; } } From 451a4f3bf7773c988bb1c83c304370050c9e302a Mon Sep 17 00:00:00 2001 From: Yanek Voloshchuk Date: Mon, 31 Mar 2025 14:00:21 +0200 Subject: [PATCH 2/9] added mad-readonly-form-field api spec, a bit clean up --- ...eadonly-form-field-wrapper.component.scss} | 0 .../readonly-form-field-wrapper.component.ts | 3 +- ...css => readonly-form-field.component.scss} | 0 .../readonly-form-field.component.ts | 7 +- .../alert-demo-api-spec.component.scss | 24 --- .../alert-demo-api-spec.component.ts | 1 - .../read-only-demo-api-spec.component.html | 139 ++++++++++++++++++ .../read-only-demo-api-spec.component.ts | 8 + .../read-only-demo.component.html | 21 ++- .../read-only-demo.component.scss | 15 -- .../read-only-demo.component.ts | 4 +- .../read-only-field-error.component.html | 2 +- .../read-only-field.component.html | 2 +- src/styles.scss | 26 ++++ 14 files changed, 199 insertions(+), 53 deletions(-) rename projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/{readonly-form-field-wrapper.component.css => readonly-form-field-wrapper.component.scss} (100%) rename projects/material-addons/src/lib/readonly/readonly-form-field/{readonly-form-field.component.css => readonly-form-field.component.scss} (100%) delete mode 100644 src/app/component-demos/alert-demo/alert-demo-api-spec/alert-demo-api-spec.component.scss create mode 100644 src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.html create mode 100644 src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.ts delete mode 100644 src/app/component-demos/read-only-demo/read-only-demo.component.scss diff --git a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.css b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.scss similarity index 100% rename from projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.css rename to projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.scss diff --git a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts index 9d2c30a8..01293f51 100644 --- a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts +++ b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts @@ -24,7 +24,7 @@ import { ObserversModule } from '@angular/cdk/observers'; @Component({ selector: 'mad-readonly-form-field-wrapper', templateUrl: './readonly-form-field-wrapper.component.html', - styleUrls: ['./readonly-form-field-wrapper.component.css'], + styleUrls: ['./readonly-form-field-wrapper.component.scss'], viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }], imports: [NgIf, ReadOnlyFormFieldComponent, ObserversModule], changeDetection: ChangeDetectionStrategy.OnPush, @@ -74,7 +74,6 @@ export class ReadOnlyFormFieldWrapperComponent implements AfterViewInit, OnChang * Otherwise, the defined rows-value will be used */ @Input() shrinkIfEmpty = false; - @Input() hideIconInReadOnlyMode = false; /** * suffix iocon */ diff --git a/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.css b/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.scss similarity index 100% rename from projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.css rename to projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.scss diff --git a/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.ts b/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.ts index cfe7ccc5..02ce9023 100644 --- a/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.ts +++ b/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.ts @@ -1,5 +1,6 @@ import { - AfterViewInit, ChangeDetectionStrategy, + AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -30,11 +31,11 @@ import { MatFormFieldModule } from '@angular/material/form-field'; @Component({ selector: 'mad-readonly-form-field', templateUrl: './readonly-form-field.component.html', - styleUrls: ['./readonly-form-field.component.css'], + styleUrls: ['./readonly-form-field.component.scss'], imports: [MatFormFieldModule, NgIf, MatInputModule, FormsModule, NgStyle, NgClass, MatTooltipModule, TextFieldModule, MatIconModule], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ReadOnlyFormFieldComponent implements OnChanges, AfterViewInit { +export class ReadOnlyFormFieldComponent implements OnChanges, AfterViewInit { @Input() useProjectedContent: boolean = false; @Input() value?: any; @Input() label!: string; diff --git a/src/app/component-demos/alert-demo/alert-demo-api-spec/alert-demo-api-spec.component.scss b/src/app/component-demos/alert-demo/alert-demo-api-spec/alert-demo-api-spec.component.scss deleted file mode 100644 index 421d448b..00000000 --- a/src/app/component-demos/alert-demo/alert-demo-api-spec/alert-demo-api-spec.component.scss +++ /dev/null @@ -1,24 +0,0 @@ -.api-specification { - font-family: Arial, sans-serif; -} - -.api-specification h2 { - color: #333; -} - -.api-specification table { - width: 100%; - border-collapse: collapse; - margin-bottom: 20px; -} - -.api-specification th, -.api-specification td { - border: 1px solid #ccc; - padding: 8px; - text-align: left; -} - -.api-specification th { - background-color: #f2f2f2; -} diff --git a/src/app/component-demos/alert-demo/alert-demo-api-spec/alert-demo-api-spec.component.ts b/src/app/component-demos/alert-demo/alert-demo-api-spec/alert-demo-api-spec.component.ts index f07cf4a8..76621b37 100644 --- a/src/app/component-demos/alert-demo/alert-demo-api-spec/alert-demo-api-spec.component.ts +++ b/src/app/component-demos/alert-demo/alert-demo-api-spec/alert-demo-api-spec.component.ts @@ -5,6 +5,5 @@ import { CommonModule } from '@angular/common'; selector: 'app-alert-demo-api-spec', imports: [CommonModule], templateUrl: './alert-demo-api-spec.component.html', - styleUrl: './alert-demo-api-spec.component.scss', }) export class AlertDemoApiSpecComponent {} diff --git a/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.html b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.html new file mode 100644 index 00000000..1eb8c3db --- /dev/null +++ b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.html @@ -0,0 +1,139 @@ +
+

Properties

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDefault ValueDescription
labelstring'' + Label shown inside the floating mat-label. +
valueany'-' if emptyThe value to display. Supports formatting for numbers.
textAlign'left' | 'right''left'Alignment of the text in the field.
formatNumberbooleanfalseIf true, formats value using NumberFormatService.
decimalPlacesnumber2Number of decimal places to display when formatting numbers.
autofillDecimalsbooleanfalseIf true, ensures trailing decimal places are filled.
unitstring | nullnullUnit string (e.g. %, EUR) injected next to the value.
unitPosition'left' | 'right''left'Placement of unit relative to the value.
errorMessagestring | nullnullOptional error message shown as mat-error.
idstring''ID for the input/textarea field.
suffixstring''Material icon name shown as matSuffix.
prefixstring''Material icon name shown as matPrefix.
shrinkIfEmptybooleanfalseShrinks to 1 row in multiline mode if value is empty or undefined.
multilinebooleanfalseIf true, displays a textarea instead of an input.
rowsnumber''Row count for textarea when multiline is true.
multilineAutoSizebooleanfalseEnables Angular CDK auto-sizing on textarea.
useProjectedContentbooleanfalseIf true, hides default rendering and shows projected custom content.
+ +

Events

+ + + + + + + + + + + + + + + + + +
EventDescription
suffixClickedEmitterEmits when suffix icon is clicked.
prefixClickedEmitterEmits when prefix icon is clicked.
+
diff --git a/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.ts b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.ts new file mode 100644 index 00000000..44879a3c --- /dev/null +++ b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-read-only-demo-api-spec', + imports: [], + templateUrl: './read-only-demo-api-spec.component.html', +}) +export class ReadOnlyDemoApiSpecComponent {} diff --git a/src/app/component-demos/read-only-demo/read-only-demo.component.html b/src/app/component-demos/read-only-demo/read-only-demo.component.html index e1b4969e..fb340c2b 100644 --- a/src/app/component-demos/read-only-demo/read-only-demo.component.html +++ b/src/app/component-demos/read-only-demo/read-only-demo.component.html @@ -1,16 +1,29 @@ +

A reusable Angular Material-based read-only field component. Displays a value inside a styled mat-form-field with optional formatting, tooltips, unit display, and icons.

+

Use When

+
    +
  • You need to display a formatted, read-only value that aligns with Angular Material form styling.
  • +
  • You want to show a tooltip on overflow, unit suffixes (like %, €), or prefix/suffix icons.
  • +
  • You want to reuse consistent read-only field styles across your application.
  • +
+

Behavior

+
    +
  • If value is null, undefined, or empty, it defaults to '-'.
  • +
  • When formatNumber is enabled, the value is formatted using NumberFormatService based on decimalPlaces and autofillDecimals.
  • +
  • If unit is set, a span is dynamically injected into the mat-form-field and placed before or after the value based on unitPosition.
  • +
  • The component checks whether the value overflows the field and shows a matTooltip only in that case.
  • +
  • Works seamlessly with Angular Material's layout for form fields, suffixes, prefixes, and errors.
  • +

Import the ReadOnlyFormFieldModule in your app.module.ts and use it via <mad-readonly-form-field> - You can either make a readonly form field unchangeable or changeable by using the readonly flag. -

-

- Undefined or null values are automatically handled from the readonly form field. Fields where not data is inputted are shown with an "-".

There is als an option to set error messages for the readonly field

+

API Specification

+

The changeable version needs a wrapper around the form field, therefore, it is a different component.

diff --git a/src/app/component-demos/read-only-demo/read-only-demo.component.scss b/src/app/component-demos/read-only-demo/read-only-demo.component.scss deleted file mode 100644 index 06c78e20..00000000 --- a/src/app/component-demos/read-only-demo/read-only-demo.component.scss +++ /dev/null @@ -1,15 +0,0 @@ -@use 'styles'; -@use '@porscheinformatik/material-addons/themes/common/theme'; - -.example { - background: theme.get-selection-background(); - padding: 20px; - margin: 10px; - border-radius: 2px; -} - -.source-code-button { - position: absolute; - right: 10px; - top: 10px; -} diff --git a/src/app/component-demos/read-only-demo/read-only-demo.component.ts b/src/app/component-demos/read-only-demo/read-only-demo.component.ts index e5e78aa4..839ae024 100644 --- a/src/app/component-demos/read-only-demo/read-only-demo.component.ts +++ b/src/app/component-demos/read-only-demo/read-only-demo.component.ts @@ -5,12 +5,12 @@ import { ReadOnlyFieldWrapperComponent } from '../../example-components/read-onl import { ReadOnlyFieldErrorComponent } from '../../example-components/read-only-field-error/read-only-field-error.component'; import { ExampleViewerComponent } from '../../components/example-viewer/example-viewer.component'; import { TextCodeComponent } from '../../components/text-code/text-code.component'; +import { ReadOnlyDemoApiSpecComponent } from './read-only-demo-api-spec/read-only-demo-api-spec.component'; @Component({ selector: 'app-read-only-demo', templateUrl: './read-only-demo.component.html', - styleUrls: ['./read-only-demo.component.scss'], - imports: [TextCodeComponent, ExampleViewerComponent], + imports: [TextCodeComponent, ExampleViewerComponent, ReadOnlyDemoApiSpecComponent], }) export class ReadOnlyDemoComponent { readOnlyFormFieldComponent = new Example(ReadOnlyFieldComponent, 'read-only-field', 'Read only form field - unchangeable'); diff --git a/src/app/example-components/read-only-field-error/read-only-field-error.component.html b/src/app/example-components/read-only-field-error/read-only-field-error.component.html index 8f5736ee..6eed8ed3 100644 --- a/src/app/example-components/read-only-field-error/read-only-field-error.component.html +++ b/src/app/example-components/read-only-field-error/read-only-field-error.component.html @@ -1 +1 @@ - + diff --git a/src/app/example-components/read-only-field/read-only-field.component.html b/src/app/example-components/read-only-field/read-only-field.component.html index effebc19..60db2b3b 100644 --- a/src/app/example-components/read-only-field/read-only-field.component.html +++ b/src/app/example-components/read-only-field/read-only-field.component.html @@ -10,7 +10,7 @@ -TADA Date: Mon, 31 Mar 2025 18:35:33 +0200 Subject: [PATCH 3/9] added tests for mad-readonly-form-field and mad-readonly-form-field-wrapper, remove content observer which cause performance issues --- ...readonly-form-field-wrapper.component.html | 2 +- ...donly-form-field-wrapper.component.spec.ts | 81 ++++++++++++++++++ .../readonly-form-field-wrapper.component.ts | 5 -- .../readonly-form-field.component.spec.ts | 82 +++++++++++++++++++ 4 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.spec.ts create mode 100644 projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.spec.ts diff --git a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.html b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.html index 31035d5d..3c5ef812 100644 --- a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.html +++ b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.spec.ts b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.spec.ts new file mode 100644 index 00000000..d95d20a7 --- /dev/null +++ b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.spec.ts @@ -0,0 +1,81 @@ +import { Component, DebugElement } from '@angular/core'; +import { ReadOnlyFormFieldWrapperComponent } from './readonly-form-field-wrapper.component'; +import { ReadOnlyFormFieldComponent } from '../readonly-form-field/readonly-form-field.component'; +import { FormControl, FormGroup, FormGroupDirective, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { ReadOnlyFormFieldModule } from '../readonly-form-field.module'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInput } from '@angular/material/input'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +@Component({ + template: ` +
+ + + Test Label + + + +
+ `, + standalone: true, + imports: [ReadOnlyFormFieldModule, MatFormFieldModule, FormsModule, ReactiveFormsModule, MatInput], +}) +class WrapperTestHostComponent { + readonly = true; + value = 'Test Value'; + form = new FormGroup({ test: new FormControl('Initial Value') }); +} + +describe('ReadOnlyFormFieldWrapperComponent', () => { + let fixture: ComponentFixture; + let hostComponent: WrapperTestHostComponent; + let wrapperDebugEl: DebugElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WrapperTestHostComponent, NoopAnimationsModule], + providers: [FormGroupDirective], + }).compileComponents(); + + fixture = TestBed.createComponent(WrapperTestHostComponent); + hostComponent = fixture.componentInstance; + wrapperDebugEl = fixture.debugElement; + fixture.detectChanges(); + }); + + it('should create the host and wrapper component', () => { + expect(hostComponent).toBeTruthy(); + const wrapper = wrapperDebugEl.query(By.directive(ReadOnlyFormFieldWrapperComponent)); + expect(wrapper).toBeTruthy(); + }); + + it('should render readonly component when readonly is true', () => { + const readonlyField = wrapperDebugEl.query(By.directive(ReadOnlyFormFieldComponent)); + expect(readonlyField).toBeTruthy(); + }); + + it('should render projected content when readonly is false', () => { + hostComponent.readonly = false; + fixture.detectChanges(); + const input = wrapperDebugEl.query(By.css('input')); + expect(input).toBeTruthy(); + expect(input.nativeElement.value).toBe('Initial Value'); + }); + + it('should emit suffixClickedEmitter', () => { + const wrapperInstance = wrapperDebugEl.query(By.directive(ReadOnlyFormFieldWrapperComponent)).componentInstance; + jest.spyOn(wrapperInstance.suffixClickedEmitter, 'emit'); + wrapperInstance.suffixClicked(); + expect(wrapperInstance.suffixClickedEmitter.emit).toHaveBeenCalled(); + }); + + it('should emit prefixClickedEmitter', () => { + const wrapperInstance = wrapperDebugEl.query(By.directive(ReadOnlyFormFieldWrapperComponent)).componentInstance; + jest.spyOn(wrapperInstance.prefixClickedEmitter, 'emit'); + wrapperInstance.prefixClicked(); + expect(wrapperInstance.prefixClickedEmitter.emit).toHaveBeenCalled(); + }); +}); diff --git a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts index 01293f51..0a681c91 100644 --- a/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts +++ b/projects/material-addons/src/lib/readonly/readonly-form-field-wrapper/readonly-form-field-wrapper.component.ts @@ -128,11 +128,6 @@ export class ReadOnlyFormFieldWrapperComponent implements AfterViewInit, OnChang this.prefixClickedEmitter.emit(null); } - onContentChange(): void { - this.extractLabel(); - this.extractValue(); - } - private syncView(): void { if (!this.originalContent) { return; diff --git a/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.spec.ts b/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.spec.ts new file mode 100644 index 00000000..657b8be2 --- /dev/null +++ b/projects/material-addons/src/lib/readonly/readonly-form-field/readonly-form-field.component.spec.ts @@ -0,0 +1,82 @@ +import { ReadOnlyFormFieldComponent } from './readonly-form-field.component'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NumberFormatService } from '../../numeric-field/number-format.service'; +import { ChangeDetectorRef, ElementRef, Renderer2 } from '@angular/core'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { By } from '@angular/platform-browser'; + +describe('ReadOnlyFormFieldComponent', () => { + let component: ReadOnlyFormFieldComponent; + let fixture: ComponentFixture; + let numberFormatService: NumberFormatService; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ReadOnlyFormFieldComponent, NoopAnimationsModule], + providers: [ + NumberFormatService, + Renderer2, + ChangeDetectorRef, + { provide: ElementRef, useValue: new ElementRef(document.createElement('div')) }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(ReadOnlyFormFieldComponent); + component = fixture.componentInstance; + numberFormatService = TestBed.inject(NumberFormatService); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should fallback to dash if value is not set', () => { + component.value = undefined; + component.ngOnChanges({}); + expect(component.value).toBe('-'); + }); + + it('should format number if formatNumber is true', () => { + const spy = jest.spyOn(numberFormatService, 'format').mockReturnValue('1,234.00'); + component.value = 1234; + component.formatNumber = true; + component.ngOnChanges({}); + expect(spy).toHaveBeenCalled(); + expect(component.value).toBe('1,234.00'); + }); + + it('should emit suffixClickedEmitter when suffix icon is clicked', () => { + const spy = jest.spyOn(component.suffixClickedEmitter, 'emit'); + component.suffix = 'info'; + fixture.detectChanges(); + const suffixIcon = fixture.debugElement.query(By.css('[data-cy="suffix-icon"]')); + suffixIcon.triggerEventHandler('click'); + expect(spy).toHaveBeenCalled(); + }); + + it('should emit prefixClickedEmitter when prefix icon is clicked', () => { + const spy = jest.spyOn(component.prefixClickedEmitter, 'emit'); + component.prefix = 'info'; + fixture.detectChanges(); + const prefixIcon = fixture.debugElement.query(By.css('[data-cy="prefix-icon"]')); + prefixIcon.triggerEventHandler('click'); + expect(spy).toHaveBeenCalled(); + }); + + it('should compute tooltip text correctly for unit position right', () => { + component.value = 123; + component.unit = '%'; + component.unitPosition = 'right'; + const tooltip = component['calculateToolTipText'](); + expect(tooltip).toBe('123 %'); + }); + + it('should compute tooltip text correctly for unit position left', () => { + component.value = 123; + component.unit = '$'; + component.unitPosition = 'left'; + const tooltip = component['calculateToolTipText'](); + expect(tooltip).toBe('$ 123'); + }); +}); From 83af62ef3b37fdcb19e4ec5edd3fdef150c210e2 Mon Sep 17 00:00:00 2001 From: Yanek Voloshchuk Date: Mon, 31 Mar 2025 19:58:34 +0200 Subject: [PATCH 4/9] added mad-readonly-form-field-wrapper api spec --- ...-only-wrapper-demo-api-spec.component.html | 133 ++++++++++++++++++ ...ad-only-wrapper-demo-api-spec.component.ts | 8 ++ .../read-only-demo.component.html | 38 ++++- .../read-only-demo.component.scss | 5 + .../read-only-demo.component.ts | 4 +- 5 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.html create mode 100644 src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.ts create mode 100644 src/app/component-demos/read-only-demo/read-only-demo.component.scss diff --git a/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.html b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.html new file mode 100644 index 00000000..87b61afe --- /dev/null +++ b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.html @@ -0,0 +1,133 @@ +
+

Properties

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDefault ValueDescription
readonlybooleantrue + If true, shows the read-only view. If false, shows projected content. +
valueany'-' if emptyThe value to display in read-only mode. Auto-extracted if not set.
textAlign'left' | 'right''left'Text alignment for read-only display.
formatNumberbooleanfalseWhether to format numeric value in read-only mode.
decimalPlacesnumber2Decimal precision for formatting numbers.
autofillDecimalsbooleanfalseIf true, fills trailing decimals.
unitstring | nullnullOptional unit symbol to show next to the value (e.g. %, €).
unitPosition'left' | 'right''left'Position of the unit relative to the value.
errorMessagestring | nullnullError text to show under the readonly field.
idstring''DOM ID passed through to the internal field.
suffixstring''Material icon name to use as suffix in read-only view.
prefixstring''Material icon name to use as prefix in read-only view.
shrinkIfEmptybooleanfalseShrinks to 1 row if value is missing and multiline is enabled.
multilinebooleanfalseEnables rendering a textarea in read-only mode.
rowsnumber''Row count for the textarea in multiline mode.
multilineAutoSizebooleanfalseEnables Angular CDK autosizing on multiline readonly field.
+ +

Events

+ + + + + + + + + + + + + + + + + +
EventDescription
suffixClickedEmitterEmits when suffix icon is clicked.
prefixClickedEmitterEmits when prefix icon is clicked.
+
diff --git a/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.ts b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.ts new file mode 100644 index 00000000..916c6d2c --- /dev/null +++ b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-read-only-wrapper-demo-api-spec', + imports: [], + templateUrl: './read-only-wrapper-demo-api-spec.component.html', +}) +export class ReadOnlyWrapperDemoApiSpecComponent {} diff --git a/src/app/component-demos/read-only-demo/read-only-demo.component.html b/src/app/component-demos/read-only-demo/read-only-demo.component.html index fb340c2b..7cd4cad6 100644 --- a/src/app/component-demos/read-only-demo/read-only-demo.component.html +++ b/src/app/component-demos/read-only-demo/read-only-demo.component.html @@ -18,12 +18,40 @@

Behavior

<mad-readonly-form-field>

- +

There is als an option to set error messages for the readonly field

- +

API Specification

- + -

The changeable version needs a wrapper around the form field, therefore, it is a different component.

- +

Readonly Form Field Wrapper

+

A flexible wrapper component that toggles between:

+
    +
  • An editable form field (projected via ng-content).
  • +
  • A read-only view powered by mad-readonly-form-field.
  • +
+

Use When

+
    +
  • You want to toggle between edit and read-only modes for Angular Material form fields.
  • +
  • You want to display consistent read-only UI using the same styling, formatting, and layout as your forms.
  • +
  • You want to automatically extract value and label from projected content to keep markup DRY.
  • +
+

Behavior

+
    +
  • If readonly = false: the component renders projected ng-content as-is (e.g. a standard mat-form-field).
  • +
  • If readonly = true: it displays mad-readonly-form-field with:
  • +
      +
    • the provided value (or auto-extracted via formControlName)
    • +
    • the extracted mat-label (automatically parsed from projected content)
    • +
    +
  • Maintains full layout consistency with Angular Material components.
  • +
+

+ Import the ReadOnlyFormFieldModule in your app.module.ts and use it via + <mad-readonly-form-field-wrapper> +

+

This allows seamless switching between edit and view modes without duplicating layout or logic.

+ +

API Specification

+ diff --git a/src/app/component-demos/read-only-demo/read-only-demo.component.scss b/src/app/component-demos/read-only-demo/read-only-demo.component.scss new file mode 100644 index 00000000..96dbc149 --- /dev/null +++ b/src/app/component-demos/read-only-demo/read-only-demo.component.scss @@ -0,0 +1,5 @@ +h1 { + font-size: 40px; + font-weight: 300; + line-height: 1.1em; +} diff --git a/src/app/component-demos/read-only-demo/read-only-demo.component.ts b/src/app/component-demos/read-only-demo/read-only-demo.component.ts index 839ae024..193c47aa 100644 --- a/src/app/component-demos/read-only-demo/read-only-demo.component.ts +++ b/src/app/component-demos/read-only-demo/read-only-demo.component.ts @@ -6,11 +6,13 @@ import { ReadOnlyFieldErrorComponent } from '../../example-components/read-only- import { ExampleViewerComponent } from '../../components/example-viewer/example-viewer.component'; import { TextCodeComponent } from '../../components/text-code/text-code.component'; import { ReadOnlyDemoApiSpecComponent } from './read-only-demo-api-spec/read-only-demo-api-spec.component'; +import { ReadOnlyWrapperDemoApiSpecComponent } from './read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component'; @Component({ selector: 'app-read-only-demo', templateUrl: './read-only-demo.component.html', - imports: [TextCodeComponent, ExampleViewerComponent, ReadOnlyDemoApiSpecComponent], + styleUrls: ['./read-only-demo.component.scss'], + imports: [TextCodeComponent, ExampleViewerComponent, ReadOnlyDemoApiSpecComponent, ReadOnlyWrapperDemoApiSpecComponent], }) export class ReadOnlyDemoComponent { readOnlyFormFieldComponent = new Example(ReadOnlyFieldComponent, 'read-only-field', 'Read only form field - unchangeable'); From 6c8a72c9b664276caf9ddaec5b6952d04771dc03 Mon Sep 17 00:00:00 2001 From: Yanek Voloshchuk Date: Mon, 31 Mar 2025 20:01:34 +0200 Subject: [PATCH 5/9] added mad-readonly-form-field-wrapper api spec --- .../read-only-demo/read-only-demo.component.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app/component-demos/read-only-demo/read-only-demo.component.html b/src/app/component-demos/read-only-demo/read-only-demo.component.html index 7cd4cad6..c3a9561d 100644 --- a/src/app/component-demos/read-only-demo/read-only-demo.component.html +++ b/src/app/component-demos/read-only-demo/read-only-demo.component.html @@ -1,9 +1,9 @@

A reusable Angular Material-based read-only field component. Displays a value inside a styled mat-form-field with optional formatting, tooltips, unit display, and icons.

Use When

    -
  • You need to display a formatted, read-only value that aligns with Angular Material form styling.
  • -
  • You want to show a tooltip on overflow, unit suffixes (like %, €), or prefix/suffix icons.
  • -
  • You want to reuse consistent read-only field styles across your application.
  • +
  • Display a formatted, read-only value that aligns with Angular Material form styling.
  • +
  • Show a tooltip on overflow, unit suffixes (like %, €), or prefix/suffix icons.
  • +
  • Reuse consistent read-only field styles across your application.

Behavior

    @@ -33,9 +33,8 @@

    Readonly Form Field Wrapper

Use When

    -
  • You want to toggle between edit and read-only modes for Angular Material form fields.
  • -
  • You want to display consistent read-only UI using the same styling, formatting, and layout as your forms.
  • -
  • You want to automatically extract value and label from projected content to keep markup DRY.
  • +
  • Toggle between edit and read-only modes for Angular Material form fields.
  • +
  • Display consistent read-only UI using the same styling, formatting, and layout as your forms.

Behavior

    From ebede42e5e8594e5d8bb223c817a04937d629e96 Mon Sep 17 00:00:00 2001 From: Yanek Voloshchuk Date: Mon, 31 Mar 2025 20:12:57 +0200 Subject: [PATCH 6/9] small icons alignment for alert component --- projects/material-addons/src/lib/alert/alert.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/material-addons/src/lib/alert/alert.component.scss b/projects/material-addons/src/lib/alert/alert.component.scss index 97499395..98309ee7 100644 --- a/projects/material-addons/src/lib/alert/alert.component.scss +++ b/projects/material-addons/src/lib/alert/alert.component.scss @@ -26,6 +26,7 @@ .alert .icon { margin-right: 8px; + margin-top: 4px; } .alert .message { @@ -35,7 +36,6 @@ .alert .close-btn { color: inherit; margin-left: auto; - padding: 0; } .alert .action-btn { From 4567b7240286cdb199d79fa4acd84bfb92fc2815 Mon Sep 17 00:00:00 2001 From: Yanek Voloshchuk Date: Mon, 31 Mar 2025 20:19:00 +0200 Subject: [PATCH 7/9] provide version and doc --- README.md | 1 + projects/material-addons/package.json | 2 +- projects/material-addons/src/version.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 08305672..36bf223e 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ The versioning of material-addons is based on the Angular version. The Angular v _Hint: Changes marked as **visible change** directly affect your application during version upgrade. **Breaking** requires your attention during upgrade._ +- **19.0.3**: Fix ReadonlyFormField/Wrapper: fix performance issues - **19.0.2**: bugfix for version: fix dist output - **19.0.0**: Upgrade to Angular 19 - **18.0.5**: FileUpload: Added additional param as 'removable' which allows user to remove file from fileList [#212](https://github.com/porscheinformatik/material-addons/pull/212) diff --git a/projects/material-addons/package.json b/projects/material-addons/package.json index 0085d64c..8f4cbb11 100644 --- a/projects/material-addons/package.json +++ b/projects/material-addons/package.json @@ -1,6 +1,6 @@ { "name": "@porscheinformatik/material-addons", - "version": "19.0.1", + "version": "19.0.3", "description": "Custom theme and components for Angular Material", "homepage": "https://github.com/porscheinformatik/material-addons", "repository": { diff --git a/projects/material-addons/src/version.ts b/projects/material-addons/src/version.ts index 8ba38100..c1c723c8 100644 --- a/projects/material-addons/src/version.ts +++ b/projects/material-addons/src/version.ts @@ -1 +1 @@ -export const VERSION = '19.0.1'; +export const VERSION = '19.0.3'; From fa2d208cbc41e2534604db1bd39ead902dd55c62 Mon Sep 17 00:00:00 2001 From: Yanek Voloshchuk Date: Tue, 1 Apr 2025 10:21:21 +0200 Subject: [PATCH 8/9] prettier fix --- .../read-only-demo-api-spec.component.html | 242 +++++++++--------- ...-only-wrapper-demo-api-spec.component.html | 230 +++++++++-------- .../read-only-demo.component.html | 32 ++- 3 files changed, 256 insertions(+), 248 deletions(-) diff --git a/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.html b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.html index 1eb8c3db..3f709e2f 100644 --- a/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.html +++ b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-demo-api-spec.component.html @@ -2,138 +2,136 @@

    Properties

    - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PropertyTypeDefault ValueDescription
    PropertyTypeDefault ValueDescription
    labelstring'' - Label shown inside the floating mat-label. -
    valueany'-' if emptyThe value to display. Supports formatting for numbers.
    textAlign'left' | 'right''left'Alignment of the text in the field.
    formatNumberbooleanfalseIf true, formats value using NumberFormatService.
    decimalPlacesnumber2Number of decimal places to display when formatting numbers.
    autofillDecimalsbooleanfalseIf true, ensures trailing decimal places are filled.
    unitstring | nullnullUnit string (e.g. %, EUR) injected next to the value.
    unitPosition'left' | 'right''left'Placement of unit relative to the value.
    errorMessagestring | nullnullOptional error message shown as mat-error.
    idstring''ID for the input/textarea field.
    suffixstring''Material icon name shown as matSuffix.
    prefixstring''Material icon name shown as matPrefix.
    shrinkIfEmptybooleanfalseShrinks to 1 row in multiline mode if value is empty or undefined.
    multilinebooleanfalseIf true, displays a textarea instead of an input.
    rowsnumber''Row count for textarea when multiline is true.
    multilineAutoSizebooleanfalseEnables Angular CDK auto-sizing on textarea.
    useProjectedContentbooleanfalseIf true, hides default rendering and shows projected custom content.
    labelstring''Label shown inside the floating mat-label.
    valueany'-' if emptyThe value to display. Supports formatting for numbers.
    textAlign'left' | 'right''left'Alignment of the text in the field.
    formatNumberbooleanfalseIf true, formats value using NumberFormatService.
    decimalPlacesnumber2Number of decimal places to display when formatting numbers.
    autofillDecimalsbooleanfalseIf true, ensures trailing decimal places are filled.
    unitstring | nullnullUnit string (e.g. %, EUR) injected next to the value.
    unitPosition'left' | 'right''left'Placement of unit relative to the value.
    errorMessagestring | nullnullOptional error message shown as mat-error.
    idstring''ID for the input/textarea field.
    suffixstring''Material icon name shown as matSuffix.
    prefixstring''Material icon name shown as matPrefix.
    shrinkIfEmptybooleanfalseShrinks to 1 row in multiline mode if value is empty or undefined.
    multilinebooleanfalseIf true, displays a textarea instead of an input.
    rowsnumber''Row count for textarea when multiline is true.
    multilineAutoSizebooleanfalseEnables Angular CDK auto-sizing on textarea.
    useProjectedContentbooleanfalseIf true, hides default rendering and shows projected custom content.

    Events

    - - - - + + + + - - - - - - - - + + + + + + + +
    EventDescription
    EventDescription
    suffixClickedEmitterEmits when suffix icon is clicked.
    prefixClickedEmitterEmits when prefix icon is clicked.
    suffixClickedEmitterEmits when suffix icon is clicked.
    prefixClickedEmitterEmits when prefix icon is clicked.
diff --git a/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.html b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.html index 87b61afe..9d75e8ad 100644 --- a/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.html +++ b/src/app/component-demos/read-only-demo/read-only-demo-api-spec/read-only-wrapper-demo-api-spec.component.html @@ -2,132 +2,130 @@

Properties

- - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDefault ValueDescription
PropertyTypeDefault ValueDescription
readonlybooleantrue - If true, shows the read-only view. If false, shows projected content. -
valueany'-' if emptyThe value to display in read-only mode. Auto-extracted if not set.
textAlign'left' | 'right''left'Text alignment for read-only display.
formatNumberbooleanfalseWhether to format numeric value in read-only mode.
decimalPlacesnumber2Decimal precision for formatting numbers.
autofillDecimalsbooleanfalseIf true, fills trailing decimals.
unitstring | nullnullOptional unit symbol to show next to the value (e.g. %, €).
unitPosition'left' | 'right''left'Position of the unit relative to the value.
errorMessagestring | nullnullError text to show under the readonly field.
idstring''DOM ID passed through to the internal field.
suffixstring''Material icon name to use as suffix in read-only view.
prefixstring''Material icon name to use as prefix in read-only view.
shrinkIfEmptybooleanfalseShrinks to 1 row if value is missing and multiline is enabled.
multilinebooleanfalseEnables rendering a textarea in read-only mode.
rowsnumber''Row count for the textarea in multiline mode.
multilineAutoSizebooleanfalseEnables Angular CDK autosizing on multiline readonly field.
readonlybooleantrueIf true, shows the read-only view. If false, shows projected content.
valueany'-' if emptyThe value to display in read-only mode. Auto-extracted if not set.
textAlign'left' | 'right''left'Text alignment for read-only display.
formatNumberbooleanfalseWhether to format numeric value in read-only mode.
decimalPlacesnumber2Decimal precision for formatting numbers.
autofillDecimalsbooleanfalseIf true, fills trailing decimals.
unitstring | nullnullOptional unit symbol to show next to the value (e.g. %, €).
unitPosition'left' | 'right''left'Position of the unit relative to the value.
errorMessagestring | nullnullError text to show under the readonly field.
idstring''DOM ID passed through to the internal field.
suffixstring''Material icon name to use as suffix in read-only view.
prefixstring''Material icon name to use as prefix in read-only view.
shrinkIfEmptybooleanfalseShrinks to 1 row if value is missing and multiline is enabled.
multilinebooleanfalseEnables rendering a textarea in read-only mode.
rowsnumber''Row count for the textarea in multiline mode.
multilineAutoSizebooleanfalseEnables Angular CDK autosizing on multiline readonly field.

Events

- - - - + + + + - - - - - - - - + + + + + + + +
EventDescription
EventDescription
suffixClickedEmitterEmits when suffix icon is clicked.
prefixClickedEmitterEmits when prefix icon is clicked.
suffixClickedEmitterEmits when suffix icon is clicked.
prefixClickedEmitterEmits when prefix icon is clicked.
diff --git a/src/app/component-demos/read-only-demo/read-only-demo.component.html b/src/app/component-demos/read-only-demo/read-only-demo.component.html index c3a9561d..e82c1d7d 100644 --- a/src/app/component-demos/read-only-demo/read-only-demo.component.html +++ b/src/app/component-demos/read-only-demo/read-only-demo.component.html @@ -1,4 +1,7 @@ -

A reusable Angular Material-based read-only field component. Displays a value inside a styled mat-form-field with optional formatting, tooltips, unit display, and icons.

+

+ A reusable Angular Material-based read-only field component. Displays a value inside a styled mat-form-field with optional formatting, + tooltips, unit display, and icons. +

Use When

  • Display a formatted, read-only value that aligns with Angular Material form styling.
  • @@ -9,21 +12,25 @@

    Behavior

    • If value is null, undefined, or empty, it defaults to '-'.
    • When formatNumber is enabled, the value is formatted using NumberFormatService based on decimalPlaces and autofillDecimals.
    • -
    • If unit is set, a span is dynamically injected into the mat-form-field and placed before or after the value based on unitPosition.
    • +
    • + If unit is set, a span is dynamically injected into the mat-form-field and placed before or after the value based on unitPosition. +
    • The component checks whether the value overflows the field and shows a matTooltip only in that case.
    • Works seamlessly with Angular Material's layout for form fields, suffixes, prefixes, and errors.

    - Import the ReadOnlyFormFieldModule in your app.module.ts and use it via + Import the + ReadOnlyFormFieldModule + in your app.module.ts and use it via <mad-readonly-form-field>

    - +

    There is als an option to set error messages for the readonly field

    - +

    API Specification

    - +

    Readonly Form Field Wrapper

    A flexible wrapper component that toggles between:

    @@ -38,7 +45,10 @@

    Use When

Behavior

    -
  • If readonly = false: the component renders projected ng-content as-is (e.g. a standard mat-form-field).
  • +
  • + If readonly = false: the component renders projected ng-content as-is (e.g. a standard + mat-form-field). +
  • If readonly = true: it displays mad-readonly-form-field with:
    • the provided value (or auto-extracted via formControlName)
    • @@ -47,10 +57,12 @@

      Behavior

    • Maintains full layout consistency with Angular Material components.

    - Import the ReadOnlyFormFieldModule in your app.module.ts and use it via + Import the + ReadOnlyFormFieldModule + in your app.module.ts and use it via <mad-readonly-form-field-wrapper>

    This allows seamless switching between edit and view modes without duplicating layout or logic.

    - +

    API Specification

    - + From a3adee852b6cb0c8a052423b4ad73967afa0c3ec Mon Sep 17 00:00:00 2001 From: Yanek Voloshchuk Date: Tue, 1 Apr 2025 10:23:29 +0200 Subject: [PATCH 9/9] prettier fix --- src/styles.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles.scss b/src/styles.scss index 40d3db10..a50322f7 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -79,4 +79,3 @@ a { .api-specification th { background-color: #f2f2f2; } -