Angular Mat Table inline editable template not working for empty data cell

1.2k views Asked by At

I have Angular Mat-table with inline edit .with the code below inline edit works fine for mat cell with record but for cell with null values or empty record inline edit does not works. i would be happy to have even the empty mat cell to be updated and edited with some value. Any work around that would enable this requirement enter image description here

table-basic-example.html

  <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
    
        <!--- Note that these columns can be defined in any order.
            The actual rendered columns are set as a property on the row definition" -->
    
        <!-- Position Column -->
        <ng-container matColumnDef="position">
            <th mat-header-cell *matHeaderCellDef> No. </th>
            <td mat-cell *matCellDef="let element"> {{element.position}} </td>
        </ng-container>
    
        <!-- Name Column -->
        <ng-container matColumnDef="name">
            <th mat-header-cell *matHeaderCellDef> Name </th>
            <td mat-cell *matCellDef="let element;let index = index">
                <editable (update)="updateField(index, 'name')">
                    <ng-template viewMode>
                        {{element.name}}
                    </ng-template>
                    <ng-template editMode>
                        <mat-form-field class="example-full-width">
                            <input matInput [formControl]="getControl(index, 'name')">
                            <mat-error *ngIf="getControl(index, 'name').hasError('required')">
                                Field is <strong>required</strong>
                            </mat-error>
                        </mat-form-field>
                        <!-- <input  [formControl]="getControl(index, 'name')" focusable editableOnEnter> -->
                    </ng-template>
                </editable>
            </td>
    
    
        </ng-container>
    
        <!-- Weight Column -->
        <ng-container matColumnDef="weight">
            <th mat-header-cell *matHeaderCellDef> Weight </th>
            <td mat-cell *matCellDef="let element;let index = index">
                <editable (update)="updateField(index, 'weight')">
                    <ng-template viewMode>
                        {{element.weight}}
                    </ng-template>
                    <ng-template editMode>
                        <input  [formControl]="getControl(index, 'weight')" focusable editableOnEnter>
            </ng-template>
                </editable>
            </td>
        </ng-container>
    
        <!-- Symbol Column -->
        <ng-container matColumnDef="symbol">
            <th mat-header-cell *matHeaderCellDef> Symbol </th>
            <td mat-cell *matCellDef="let element;let index = index">
                <editable (update)="updateField(index, 'symbol')">
                    <ng-template viewMode>
                        {{element.symbol}}
                    </ng-template>
                    <ng-template editMode>
                        <input  [formControl]="getControl(index, 'symbol')" focusable editableOnEnter>
            </ng-template>
                </editable>
            </td>
        </ng-container>
    
        <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
        <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
    </table>

table-basic-example.ts

import { Component } from '@angular/core';
import { FormControl, FormArray, FormGroup, Validators } from '@angular/forms';

import {CoreService} from './services/core.service';

/**
 * @title Basic use of `<table mat-table>`
 */
@Component({
  selector: 'table-basic-example',
  styleUrls: ['table-basic-example.css'],
  templateUrl: 'table-basic-example.html',
})
export class TableBasicExample {
  displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
  dataSource = this.core.list$;
  controls: FormArray;

  constructor(private core: CoreService){}

  ngOnInit() {
   
    const toGroups = this.core.list$.value.map(entity => {
      return new FormGroup({
        position:  new FormControl(entity.position, Validators.required),
        name: new FormControl(entity.name, Validators.required), 
        weight: new FormControl(entity.weight, Validators.required),
        symbol: new FormControl(entity.symbol, Validators.required)
      },{updateOn: "blur"});
    });

    this.controls = new FormArray(toGroups);

  }


  updateField(index, field) {
    const control = this.getControl(index, field);
    if (control.valid) {
      this.core.update(index,field,control.value);
    }

   }

  getControl(index, fieldName) {
    const a  = this.controls.at(index).get(fieldName) as FormControl;
    return this.controls.at(index).get(fieldName) as FormControl;
  }

}

edit-mode.directive.ts

import { Directive, TemplateRef } from '@angular/core';

@Directive({
  selector: '[editMode]'
})
export class EditModeDirective {
  constructor(public tpl: TemplateRef<any>) { }
}

editable.component.ts

import { Component, ContentChild, HostListener, ElementRef, EventEmitter, Output, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { ViewModeDirective } from './view-mode.directive';
import { EditModeDirective } from './edit-mode.directive';
import { NgControl } from '@angular/forms';
import { fromEvent, Subject } from 'rxjs';
import { switchMap, takeUntil, filter, take, switchMapTo } from 'rxjs/operators';
import { untilDestroyed } from 'ngx-take-until-destroy';

@Component({
  selector: 'editable',
  template: `
    <ng-container *ngTemplateOutlet="currentView"></ng-container>
  `,
  styleUrls: ['./editable.component.css']
})
export class EditableComponent {
  @ContentChild(ViewModeDirective) viewModeTpl: ViewModeDirective;
  @ContentChild(EditModeDirective) editModeTpl: EditModeDirective;
  @Output() update = new EventEmitter();

  editMode = new Subject();
  editMode$ = this.editMode.asObservable();

  mode: 'view' | 'edit' = 'view';


  constructor(private host: ElementRef) {
  }

  ngOnInit() {
    this.viewModeHandler();
    this.editModeHandler();
  }

  toViewMode() {
    this.update.next();
    this.mode = 'view';
  }

  private get element() {
    return this.host.nativeElement;
  }

  private viewModeHandler() {
    fromEvent(this.element, 'dblclick').pipe(
      untilDestroyed(this)
    ).subscribe(() => { 
      this.mode = 'edit';
      this.editMode.next(true);
    });
  }

  private editModeHandler() {
    const clickOutside$ = fromEvent(document, 'click').pipe(
      filter(({ target }) => this.element.contains(target) === false),
      take(1)
    )

    this.editMode$.pipe(
      switchMapTo(clickOutside$),
      untilDestroyed(this)
    ).subscribe(event => this.toViewMode());
  }

  get currentView() {
    return this.mode === 'view' ? this.viewModeTpl.tpl : this.editModeTpl.tpl;
  }

  ngOnDestroy() {
  }

}

editable.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EditableComponent } from './editable.component';
import { ViewModeDirective } from './view-mode.directive';
import { EditModeDirective } from './edit-mode.directive';
import { FocusableDirective } from './focusable.directive';
import { EditableOnEnterDirective } from './edit-on-enter.directive';
import { MaterialModule } from 'app/material/material.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ClickOutsideModule } from 'ng-click-outside';

@NgModule({
    declarations: [],
    imports: [
        CommonModule,
        MaterialModule,
        FormsModule,
        ReactiveFormsModule,
        ClickOutsideModule
    ],
    exports: [
    ]
})

export class EditableModule { }

view-mode.directive.ts

import { Directive, TemplateRef } from '@angular/core';

@Directive({
  selector: '[viewMode]'
})
export class ViewModeDirective {

  constructor(public tpl: TemplateRef<any>) { }

}
1

There are 1 answers

2
AFetter On

I found you with the problem I had. After some investigation and looking to your code we made the same mistake.

We forgot to declare all directives.

declarations: [
    ....
    EditModeDirective,
    EditableOnEnterDirective,
    ViewModeDirective,
    EditableOnClickDirective,
    FocusableDirective
  ],

Nothing in Angular show the directive have been missed. I kind of understand why.