src/app/shared/input-inline/input-inline.component.ts
| selector | hi-input-inline | 
| styleUrls | ./input-inline.component.scss | 
| templateUrl | ./input-inline.component.html | 
| Properties | 
| Methods | 
| 
 | 
| Inputs | 
| Outputs | 
| Accessors | 
| constructor() | 
| blur | |
| Type : Function | |
| Default value : (_) => {} | |
| disabled | |
| Type : boolean | |
| Default value : false | |
| editLabel | |
| Type : string | |
| Default value : 'Click to edit' | |
| errorLabel | |
| Type : string | |
| Default value : 'Invalid input value' | |
| focus | |
| Type : Function | |
| Default value : (_) => {} | |
| label | |
| Type : string | |
| Default value : '' | |
| max | |
| Type : number | |
| Default value : 99999999 | |
| maxlength | |
| Type : number | |
| Default value : 2555 | |
| min | |
| Type : number | |
| Default value : -9999 | |
| minlength | |
| Type : number | |
| Default value : 0 | |
| pattern | |
| Type : string | |
| Default value : null | |
| required | |
| Type : boolean | |
| Default value : false | |
| type | |
| Type : string | |
| Default value : 'text' | |
| value | |
| Type : any | |
| update | |
| Type : EventEmitter<string> | |
| cancel | 
| cancel() | 
| 
                        Returns :          void | 
| edit | ||||
| edit(value) | ||||
| 
                        Parameters :
                        
                         
 
                        Returns :          void | 
| hasError | 
| hasError() | 
| 
                        Returns :          boolean | 
| ngOnInit | 
| ngOnInit() | 
| 
                        Returns :          void | 
| onBlur | ||||||
| onBlur($event: Event) | ||||||
| 
                        Parameters :
                        
                         
 
                        Returns :          boolean | 
| Public registerOnChange | ||||||
| registerOnChange(fn: (_: any) => void) | ||||||
| 
                        Parameters :
                        
                         
 
                        Returns :          void | 
| Public registerOnTouched | ||||||
| registerOnTouched(fn: () => void) | ||||||
| 
                        Parameters :
                        
                         
 
                        Returns :          void | 
| writeValue | ||||||
| writeValue(value: any) | ||||||
| 
                        Parameters :
                        
                         
 
                        Returns :          void | 
| Private _value | 
| Type : string | 
| Default value : '' | 
| editing | 
| Default value : false | 
| inputControl | 
| Type : ElementRef | 
| Decorators : 
                            @ViewChild('inputControl', {static: true}) | 
| Private lastValue | 
| Type : string | 
| Default value : '' | 
| Public onChange | 
| Type : any | 
| Default value : Function.prototype | 
| Public onTouched | 
| Type : any | 
| Default value : Function.prototype | 
| value | ||||||
| get value() | ||||||
| set value(v: any) | ||||||
| 
                                        Parameters :
                                         
 
                                    Returns :          void | 
import {
  Component,
  OnInit,
  Input,
  Output,
  ViewChild,
  ElementRef,
  EventEmitter,
} from '@angular/core';
import { ControlValueAccessor } from '@angular/forms';
@Component({
  selector: 'hi-input-inline',
  templateUrl: './input-inline.component.html',
  styleUrls: ['./input-inline.component.scss'],
})
export class InputInlineComponent implements ControlValueAccessor, OnInit {
  @ViewChild('inputControl', { static: true }) inputControl: ElementRef;
  @Output('update') change: EventEmitter<string> = new EventEmitter<string>();
  @Input() label = '';
  @Input() min = -9999;
  @Input() max = 99999999;
  @Input() minlength = 0;
  @Input() maxlength = 2555;
  @Input() type = 'text';
  @Input() required = false;
  @Input() pattern: string = null;
  @Input() errorLabel = 'Invalid input value';
  @Input() editLabel = 'Click to edit';
  @Input() disabled = false;
  editing = false;
  private _value = '';
  private lastValue = '';
  // Required forControlValueAccessor interface
  public onChange: any = Function.prototype;
  public onTouched: any = Function.prototype;
  @Input()
  get value(): any {
    return this._value;
  }
  set value(v: any) {
    if (v !== this._value) {
      this._value = v;
      this.onChange(v);
    }
  }
  @Input() focus: Function = (_) => {};
  @Input() blur: Function = (_) => {};
  public registerOnChange(fn: (_: any) => {}): void {
    this.onChange = fn;
  }
  public registerOnTouched(fn: () => {}): void {
    this.onTouched = fn;
  }
  writeValue(value: any) {
    this._value = value;
  }
  constructor() {}
  ngOnInit() {}
  hasError() {
    const exp = new RegExp(this.pattern);
    if (!this.value) {
      return this.required;
    }
    if (this.pattern && !exp.test(this.value)) {
      return true;
    }
    return false;
  }
  edit(value) {
    if (this.disabled) {
      return;
    }
    this.lastValue = value;
    this.editing = true;
    setTimeout((_) => {
      this.inputControl?.nativeElement.focus();
    });
  }
  onBlur($event: Event) {
    if (this.hasError()) {
      return false;
    }
    // this.blur();
    this.editing = false;
    this.change.emit(this.value);
  }
  cancel() {
    this._value = this.lastValue;
    this.editing = false;
  }
}
<!--
  ~ Licensed to the Apache Software Foundation (ASF) under one
  ~ or more contributor license agreements.  See the NOTICE file
  ~ distributed with this work for additional information
  ~ regarding copyright ownership.  The ASF licenses this file
  ~ to you under the Apache License, Version 2.0 (the
  ~ "License"); you may not use this file except in compliance
  ~ with the License.  You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing,
  ~ software distributed under the License is distributed on an
  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  ~ KIND, either express or implied.  See the License for the
  ~ specific language governing permissions and limitations
  ~ under the License.
  -->
<section>
  <mat-form-field
    *ngIf="editing"
    class="full-width {{ hasError() ? 'error' : '' }}"
  >
    <input
      matInput
      #inputControl
      [min]="min"
      [max]="max"
      [minlength]="minlength"
      [maxlength]="maxlength"
      (focus)="focus($event)"
      (blur)="onBlur($event)"
      (keyup.enter)="onBlur($event)"
      (keyup.escape)="cancel()"
      [required]="required"
      [name]="value"
      [(ngModel)]="value"
      [type]="type"
      [placeholder]="label"
    />
    <mat-hint *ngIf="hasError()" align="start">{{ errorLabel }}</mat-hint>
    <mat-hint align="end">press ESC to cancel</mat-hint>
  </mat-form-field>
  <section *ngIf="!editing">
    <div
      class="inline-edit {{ hasError() ? 'error' : '' }}"
      [matTooltip]="editLabel"
      (click)="edit(value)"
      (focus)="edit(value)"
      tabindex="0"
    >
      <span *ngIf="value.length">{{ value }}</span>
      <span *ngIf="!value.length" class="empty-value">( Empty )</span>
    </div>
  </section>
</section>
                    ./input-inline.component.scss
                
.inline-edit {
  text-decoration: none;
  border-bottom: dashed 1px #009090;
  cursor: pointer;
  line-height: 2;
  margin: 0;
}
.error {
  color: #a94442;
}
.empty-value {
  color: gray;
  font-style: italic;
}
.full-width {
  width: 100%;
}