Inline editor

The inline editor is used to make text editable. It uses nested buttons and the input field component.

´ Loading interactive demo...
<em dt-inline-editor [(ngModel)]="sampleModel" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" ></em> <span> model: <code>{{ sampleModel }}</code> </span> export class InlineEditorDefaultExample { sampleModel = 'text content'; } <em dt-inline-editor [(ngModel)]="sampleModel" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" ></em> <span> model: <code>{{ sampleModel }}</code> </span> export class InlineEditorDefaultExample { sampleModel = 'text content'; }

Inline editing is indicated by the edit icon used in the nested button next to the editable text. The checkmark icon and abort icon used in the nested button save/discard the changes.

Behavior

While the cursor is in the input field, the enter key triggers saving it. On focus loss of the inline editing input field, the typed input will be autosaved.

dt-inline-editor is a directive that makes any text containing HTML element editable.

´ Loading interactive demo...
<em dt-inline-editor [(ngModel)]="sampleModel" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" ></em> <span> model: <code>{{ sampleModel }}</code> </span> export class InlineEditorDefaultExample { sampleModel = 'text content'; } <em dt-inline-editor [(ngModel)]="sampleModel" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" ></em> <span> model: <code>{{ sampleModel }}</code> </span> export class InlineEditorDefaultExample { sampleModel = 'text content'; }

Imports

You have to import the DtInlineEditorModule when you want to use the dt-inline-editor directive:

@NgModule({
  imports: [DtInlineEditorModule],
})
class MyModule {}

Initialization

To apply, add the dt-inline-editor attribute to the HTML element.

Options & Properties

Name Type Default Description
@Input() required boolean false To specify that the input field must not be left empty.
@Input() onRemoteSave function - A callback returning an Observable that will be triggered when the (potentially async) saving of the new value has finished. The inline editor needs to be notified so it can go back to idle state if ok or stay in editing mode if failed.
@Input() errorStateMatcher ErrorStateMatcher DefaultErrorStateMatcher A class used to control when error messages are shown.
@Input() ariaLabelSave string - Takes precedence as the save buttons's text alternative.
@Input() ariaLabelCancel string - Takes precedence as the cancel button's text alternative.
@Input() errorStateMatcher ErrorStateMatcher - An errorStateMatcher that controls when to invalidate the inline editor
@Output() saved EventEmitter<string>() - Emitted when value is saved.
@Output() cancelled EventEmitter<string>() - Emitted when editing is cancelled.
value() string '' Value of the inline editor.
idle() boolean - Whether current mode is idle (readonly).
editing() boolean - Whether current mode is editing (readonly).
saving() boolean - Whether current mode is saving (readonly).

Error messages & Validation

When a value for the input field is validated, an error message must be provided by adding a <dt-error> element inside the inline editor.

´ Loading interactive demo...
<em dt-inline-editor required [(ngModel)]="sampleModel" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" > <dt-error>Empty value not accepted!</dt-error> </em> <span> model: <code>{{ sampleModel }}</code> </span> export class InlineEditorRequiredExample { sampleModel = 'text content'; } <em dt-inline-editor required [(ngModel)]="sampleModel" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" > <dt-error>Empty value not accepted!</dt-error> </em> <span> model: <code>{{ sampleModel }}</code> </span> export class InlineEditorRequiredExample { sampleModel = 'text content'; }

By default errors are hidden initially and will be displayed on invalid form fields, after the user has interacted with the element or the parent form has been submitted. This behaviour can be customized by passing an ErrorStateMatcher to the inline editor.

An inline editor can have more than one error, it is up to the consumer to toggle which messages should be displayed. This can be done with ngIf or ngSwitch. You can use the Angular forms API with the inline editor and pass validators to it as you would with any other form field.

´ Loading interactive demo...
<form [formGroup]="queryTitleForm"> <em dt-inline-editor [(ngModel)]="value" formControlName="queryTitleControl" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" required > <dt-error *ngIf="queryTitleControl.hasError('required')"> The query title must not be empty. </dt-error> <dt-error *ngIf="queryTitleControl.hasError('minlength')"> The query title must be at least 4 characters long </dt-error> <dt-error *ngIf="hasCustomError"> Password must include the string 'barista' </dt-error> </em> </form> export class InlineEditorValidationExample { queryTitleControl = new FormControl('', [ // tslint:disable-next-line: no-unbound-method Validators.minLength(4), this.baristaValidator(), ]); queryTitleForm = new FormGroup({ queryTitleControl: this.queryTitleControl, }); value = '123'; get hasCustomError(): boolean { return this.queryTitleControl.hasError('barista'); } /** * Note that this validator function does not have to be part of the class * exporting/importing this function is preferred since it increases reusability */ baristaValidator(): ValidatorFn { // tslint:disable-next-line: no-any return (control: AbstractControl): { [key: string]: any } | null => { const required = !control.value.includes('barista'); return required ? { barista: { value: control.value } } : null; }; } } <form [formGroup]="queryTitleForm"> <em dt-inline-editor [(ngModel)]="value" formControlName="queryTitleControl" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" required > <dt-error *ngIf="queryTitleControl.hasError('required')"> The query title must not be empty. </dt-error> <dt-error *ngIf="queryTitleControl.hasError('minlength')"> The query title must be at least 4 characters long </dt-error> <dt-error *ngIf="hasCustomError"> Password must include the string 'barista' </dt-error> </em> </form> export class InlineEditorValidationExample { queryTitleControl = new FormControl('', [ // tslint:disable-next-line: no-unbound-method Validators.minLength(4), this.baristaValidator(), ]); queryTitleForm = new FormGroup({ queryTitleControl: this.queryTitleControl, }); value = '123'; get hasCustomError(): boolean { return this.queryTitleControl.hasError('barista'); } /** * Note that this validator function does not have to be part of the class * exporting/importing this function is preferred since it increases reusability */ baristaValidator(): ValidatorFn { // tslint:disable-next-line: no-any return (control: AbstractControl): { [key: string]: any } | null => { const required = !control.value.includes('barista'); return required ? { barista: { value: control.value } } : null; }; } }

Examples

JavaScript API

´ Loading interactive demo...
<em #sampleEditor dt-inline-editor [(ngModel)]="sampleModel" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" ></em> <button (click)="sampleEditor.enterEditing()">open editor</button> <button (click)="sampleEditor.saveAndQuitEditing()">save changes</button> <button (click)="sampleEditor.cancelAndQuitEditing()"> cancel changes </button> export class InlineEditorApiExample { @ViewChild('sampleEditor', { static: true }) sampleEditor: DtInlineEditor; sampleModel = 'text content'; } <em #sampleEditor dt-inline-editor [(ngModel)]="sampleModel" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" ></em> <button (click)="sampleEditor.enterEditing()">open editor</button> <button (click)="sampleEditor.saveAndQuitEditing()">save changes</button> <button (click)="sampleEditor.cancelAndQuitEditing()"> cancel changes </button> export class InlineEditorApiExample { @ViewChild('sampleEditor', { static: true }) sampleEditor: DtInlineEditor; sampleModel = 'text content'; }

Successful asynchronous operation

´ Loading interactive demo...
<em dt-inline-editor [(ngModel)]="sampleModel" [onRemoteSave]="successfulSaveFunction" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" ></em> export class InlineEditorSuccessfulExample { sampleModel = 'text content'; successfulSaveFunction(): Observable<void> { return new Observable<void>(observer => { setTimeout(() => { observer.next(); observer.complete(); }, TIMEOUT_MS); }); } } <em dt-inline-editor [(ngModel)]="sampleModel" [onRemoteSave]="successfulSaveFunction" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" ></em> export class InlineEditorSuccessfulExample { sampleModel = 'text content'; successfulSaveFunction(): Observable<void> { return new Observable<void>(observer => { setTimeout(() => { observer.next(); observer.complete(); }, TIMEOUT_MS); }); } }

Failing asynchronous operation

´ Loading interactive demo...
<em dt-inline-editor [(ngModel)]="sampleModel" [onRemoteSave]="failingSaveFunction" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" ></em> export class InlineEditorFailingExample { sampleModel = 'text content'; failingSaveFunction(): Observable<void> { return new Observable<void>(observer => { setTimeout(() => { observer.error(); }, TIMEOUT_MS); }); } } <em dt-inline-editor [(ngModel)]="sampleModel" [onRemoteSave]="failingSaveFunction" aria-label-save="Save text" aria-label-cancel="Cancel and discard changes" ></em> export class InlineEditorFailingExample { sampleModel = 'text content'; failingSaveFunction(): Observable<void> { return new Observable<void>(observer => { setTimeout(() => { observer.error(); }, TIMEOUT_MS); }); } }