3

I want when I type the name in the name input field to filter the FormArray data, and sort to what has been typed in the input box form FormArray data controls

enter image description here

 <tbody formArrayName="cards">
                  <tr class="custom" *ngFor="let card of cardsArray().controls; index as i; " [formGroupName]="i">
                    <td class="pr-0">
                      <input [attr.id]="'name'+i" class="form-control form-control-sm" formControlName="name"
                        [readonly]="true">
                    </td>
                    </tr>
                    </tbody>

4 Answers 4

2

Observables to the rescue! :)

Attach a form control to your search field, listen to the changes of it and filter the values. Return an observable to the template and use the async pipe there. Here is a sample for you, you just need to change the variable names to fit your needs:

The input with the form control:

<input [formControl]="searchCtrl" placeholder="Search"/>

Let's say your form looks like this:

this.myForm = this.fb.group({
  formArr: this.fb.array([
    this.fb.group({
      formCtrl: ['one']
    }),
    //... more
  ])
});

// just a getter for your formarray
get formArr() {
  return (this.myForm.get('formArr') as FormArray).controls;
}

Then listen in the component for the change and do the above mentioned filter. I like to put a slight debounce time before making the filter, if the user types fast.

Then the filtered formArr$ variable (which is an observable):

formArr$ = this.searchCtrl.valueChanges.pipe(
  startWith(''),
  debounceTime(300),
  switchMap((val: string) => {
    return of(this.formArr as AbstractControl[]).pipe(
      map((formArr: AbstractControl[]) =>
        formArr.filter((group: AbstractControl) => {
          return group.get('formCtrl').value
           .toLowerCase()
           .includes(val.toLowerCase());
        })
      )
    );
  })
);

Then just use the async pipe in template:

<div *ngFor="let group of formArr$ | async">
  <div formArrayName="formArr">
    <div [formGroup]="group">
      <input formControlName="formCtrl">
    </div>
  </div>
</div>

That's it! Here is a DEMO with the above code

Sign up to request clarification or add additional context in comments.

3 Comments

am using the latest Angular so its highlighting an error on map (ts(2345)) =>Argument of type 'unknown[]' is not assignable to parameter of type 'OperatorFunction<AbstractControl[], unknown>'. Type 'unknown[]' provides no match for the signature '(source: Observable<AbstractControl[]>): Observable<unknown>'
try using AbstractControl, which is the base class: map((formArr: AbstractControl[]) also: return of(this.formArr as AbstractControl[]).pipe(
This one gave me a lot of trouble, but to anyone using this as reference point; Instead of setting the [formGroupName]="i" to the index, set the [formGroup]="group" instead. This is because when you have searched for e.g. "tw", and remove the "w", the results will show "two" and "two", and now you have messed up the form group references in the list. Here is the updated stackblitz without this bug: stackblitz.com/edit/…
1

You can use pipe to filter angular FormArray data

<input type="text" placeholder="Search" 
    [(ngModel)]="searchText"/>

      <tr *ngFor="let card of cardsArray |filter:searchText ">

In components.ts

    searchText : string;

Comments

0

Thanx AT82, your example seems to work fine, but do you really need switchMap, of and map? Couldn't you simplify the filtering part to this?

formArr$ = this.searchCtrl.valueChanges.pipe(
  startWith(''),
  debounceTime(200),
  distinctUntilChanged(),
  map(val => 
    (this.formArr as AbstractControl[]).filter((group: AbstractControl) => 
          group.get('formCtrl').value
            .toLowerCase()
            .includes(val.toLowerCase())
        )
  )
);

I've modified AT82:s demo

But I'm not sure if referencing this.formArr inside the rxjs operators is good practice, since it's declared outside.

Perhaps a better solution would be to use combineLatest like this:

this.originalFormArr$ = of(this.formArr as AbstractControl[]);
this.searchValue$ = this.searchCtrl.valueChanges.pipe(startWith(''));  
this.formArr$ = combineLatest([this.originalFormArr$, this.searchValue$])
.pipe(
  debounceTime(200),
  distinctUntilChanged(),      
  map(([formArr, value]) =>
  formArr.filter((group: AbstractControl) => 
        group.get('formCtrl').value
          .toLowerCase()
          .includes(value.toLowerCase())
      )
));

I've modified AT82:s demo once again

Comments

0

Using a custom "pipe",

@Pipe({
  name: 'formarrayFilter',
})
export class FormarrayFilterPipe implements PipeTransform {
  /**
   * @areaList formarray
   * @areaname search text
   * @key      key to filter, id, name etc.
   */
  transform(areaList: any, areaname: string, key: string): any[] {
    if (!areaname || areaname.length == 0 || !key || key.length == 0) {
      return areaList;
    } else if (areaList) {
      return areaList.filter(
        (listing: FormGroup) =>
          listing
            .get(key)
            .value.toString()
            .toLocaleLowerCase()
            .indexOf(areaname.toLocaleLowerCase()) > -1
      );
    }
  }
}

HTML

<form [formGroup]="form">
  <div
    *ngFor="
      let frmGrp of formArr.controls | formarrayFilter: filterText:'name';
      let i = index;
      trackBy: trackByFn
    "
  >
    <div formArrayName="formArr">
      <div [formGroup]="frmGrp">
        {{ frmGrp.get('id').value }} <input formControlName="name" />
      </div>
    </div>
  </div>
</form>

Demo Example: Look at Stackblitz

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.