Angular Column level filters in table having reactive form and formArray

Consider below approach using reactive programing.

The Steps are as below

  • Convert all your inputs to observables
  • Set up a Subject to use as a trigger for filtering
  • Combine the data and the subject using the combineLatest([...]) operator from rxjs

Below is a working code, See this demo on stackblitz

  constructor(private fb: FormBuilder) {}
  patterns = [
    /^[.\d]+$/,
    /^(yes|no)$/i,
    /^[a-zA-Z0-9 _/]+$/,
    /^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/
  ];
  data$ = of([
    {
      name: "Sachin",
      age: 27,
      isEditable: false
    },
    {
      name: "Gopal",
      age: 27,

      isEditable: false
    },
    {
      name: "Pankaj",
      age: 24,

      isEditable: false
    }
  ]);
  filterStringSubject$ = new BehaviorSubject({});
  filterStringAction$ = this.filterStringSubject$.asObservable();
  filterString$ = this.filterStringAction$.pipe(
    map(stringObject =>
      Object.entries(stringObject).map(item => ({
        key: item[0],
        value: item[1]
      }))
    )
  );
  rowKeys$ = this.data$.pipe(
    map(data => Object.keys(data[0])),
    tap(rowKeys => {
      rowKeys.forEach(num => {
        if (num == "isEditable") return;
        this.filterStringSubject$.next({
          ...this.filterStringSubject$.value,
          [num]: ""
        });
      });
    })
  );
  keys$ = this.data$.pipe(
    map(data => [...new Set(data.map(item => Object.keys(item)).flat())])
  );
  keyPattern$ = combineLatest(this.keys$, this.data$).pipe(
    map(([keys, data]) => {
      return keys.map(item => ({
        key: item,
        pattern: this.patterns.find(pattern =>
          data.every(i => pattern.test(i[item]))
        )
      }));
    })
  );
  data_form: FormGroup;
  dataFiltered$ = combineLatest([this.data$, this.filterString$]).pipe(
    map(([data, filterString]) =>
      this.persons?.value.filter(item =>
        filterString.every(a => `${item[a.key]}`.includes(`${a.value}`))
      )
    )
  );
  dataForm$ = combineLatest([ this.data$,
    this.keyPattern$]).pipe(
tap(([data, keyPattern]) => { 
      this.data_form = this.fb.group({
        persons: this.fb.array(
          data.map(item =>
            this.fb.group(
              keyPattern.reduce(
                (prev, { key, pattern }) => ({
                  ...prev,
                  [key]: [
                    item[key],
                    [Validators.required, Validators.pattern(pattern)]
                  ]
                }),
                {}
              )
            )
          )
        )
      });
    }),
    )
  v$ = combineLatest([
    this.dataForm$,
    this.rowKeys$,
    this.filterString$,
    this.dataFiltered$
  ]).pipe(
    
    map(([, rowKeys, filterString, dataFiltered]) => ({
      rowKeys,
      filterString,
      dataFiltered
    }))
  );
  get persons(): FormArray {
    return this.data_form?.get("persons") as FormArray;
  }

  toggleEdit(j) {
    
    const currentEditStatus = this.persons.controls[j].get("isEditable").value;
    this.persons.controls[j].get("isEditable").setValue(!currentEditStatus);
  }
  filterBy(item, value) {
    this.filterStringSubject$.next({
      ...this.filterStringSubject$.value,
      [item]: value
    });
  }
  ngOnInit() { }

In your HTML

<form [formGroup]="data_form" *ngIf='v$ | async as v'>
    <table class="table table-border">

        <thead>
            <tr>
                <th>
                    name
                </th>
                <th>
                    age
                </th>
                <th><button class="btn btn-primary ">Save</button></th>
            </tr>
            <tr>
                <td *ngFor="let item of v.rowKeys">
                    <input *ngIf='item != "isEditable"' type="text"
          (input)="filterBy(item, $event.target.value)" />
        </td>

                <th></th>
            </tr>
        </thead>
        <tbody formArrayName="persons">
            <ng-container *ngFor="let item of v.dataFiltered;let j = index">
                <tr [formGroupName]="j">
                    <ng-container *ngIf="!persons.controls[j]?.get('isEditable').value; else editable">
                        <td>{{ item.name }}</td>
                        <td>{{ item.age }}</td>
                    </ng-container>
                    <ng-template #editable>
                        <td><input formControlName="name" /></td>
                        <td><input formControlName="age" /></td>
                    </ng-template>
                    <td>
                        <button (click)="toggleEdit(j)">
              {{ !persons.controls[j]?.get('isEditable').value ? "Edit": "Cancel"}}
            </button>
                    </td>
                </tr>
            </ng-container>
        </tbody>
    </table>
</form>
<h2>
    {{data_form?.status}}
</h2>

CLICK HERE to find out more related problems solutions.

Leave a Comment

Your email address will not be published.

Scroll to Top