Michael
Michael

Reputation: 460

Angular: using an Observable to display and search data

I'm developing an Angular component which is supposed to serve as a visual list of objects. I'm trying to implement the search & filtering example from ng-bootstrap. The problem I'm having is that I cannot synchronize between fetching the data and displaying the data.

I've tried migrating from using this.antraege to using this.filteredAntraege$ which is an Observable.

If I don't use the Promise between readAntraege() and its invocation in the constructor(), I end up having this.antraege being empty, so with that construct I'm trying to execute search() AFTER readAntraege() and NOT earlier.

In the template, I could just loop over this.antraege, but that would render me being not able to use the search mechanism (tried that before).

What's the missing brick to using the Observable and making this work?

Thank you in advance!

Michael

the component:

import { AlertService } from './../../_alert/alert.service';
import { Component, OnInit, PipeTransform } from '@angular/core';
import { AntragService } from '../../services/antrag.service';
import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { NgbdModalConfirmAutofocus } from '../modal-focus/modal-focus.component';
import { Router } from '@angular/router';
import { Antrag } from '../../shared/antrag.model';
import { DecimalPipe } from '@angular/common';
import { Observable } from 'rxjs';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { startWith, map, filter } from 'rxjs/operators';

@Component({
  selector: 'app-antrag-list',
  templateUrl: './antrag-list.component.html',
  styleUrls: ['./antrag-list.component.css'],
  providers: [DecimalPipe]
})
export class AntragListComponent implements OnInit {
  activeModal: NgbActiveModal;

  antraege: Antrag[];
  currentAntrag = null;
  currentIndex = -1;
  name = '';

  filteredAntraege$: Observable<Antrag[]>;
  filterfcvar: FormControl;
  filterfg: FormGroup;

  constructor(private antragService: AntragService,
    public alertService: AlertService,
    public modal: NgbActiveModal,
    private router: Router,
    private modalService: NgbModal,
    pipe: DecimalPipe,
    private fb: FormBuilder) {
    this.filterfcvar = new FormControl();
    this.filterfg = this.fb.group({filterfc: this.filterfcvar});
    this.readAntraege().then((result) => {
        // Aufbau des Datenbestandes für das Suchefeld:
        this.filteredAntraege$ = this.filterfcvar.valueChanges.pipe(
          startWith(''),
          map(text => this.search(text, pipe))
        );
      }).catch((err) => {

      });
     }

  ngOnInit(): void {
  }

  readAntraege(): any {
    return new Promise((resolve, reject) => {
      this.antragService.readAll()
        .subscribe(
          antraege => {
            this.antraege = antraege;
            console.log('readAntraege(): antraege===' + antraege);
            console.log('readAntraege(): this.antraege===' + this.antraege);
          },
          error => {
            console.log(error);
          });
      });
    }

  refresh(): void {
    this.readAntraege();
    this.currentAntrag = null;
    this.currentIndex = -1;
  }

  setCurrentAntrag(antrag, index): void {
    this.currentAntrag = antrag;
    this.currentIndex = index;
  }

  search(text: string, pipe: PipeTransform): Antrag[] {
    console.log('search: text===' + text);
    console.log('search: pipe===' + pipe);
    console.log('antrag filter beispiel: ' + this.antraege);
    return this.antraege.filter(antrag => {
      const term = text.toLowerCase();
      return antrag.antragsteller.toLowerCase().includes(term)
          || pipe.transform(antrag.gestelltbei).includes(term)
          || pipe.transform(antrag.status).includes(term);
    });
  }
}

the template:

<div class="form-group">
  <form [formGroup]="filterfg" novalidate>
    <div class="form-group form-inline">
      Volltextsuche: <input class="form-control ml-2" type="text" formControlName="filterfc"/>
    </div>
  </form>
</div>

<table class="table table-striped">
  <thead>
  <tr>
    <th scope="col">id</th>
    <th scope="col">antragsteller</th>
    <th scope="col">gestellt bei</th>
    <th scope="col">Eingangsdatum</th>
    <th scope="col">Förderhöhe</th>
    <th scope="col">Status</th>
    <th scope="col">Aktion</th>
  </tr>
  </thead>
  <tbody>
  <tr *ngFor="let antrag of filteredAntraege$ | async; index as i"  [class.active]="i == currentIndex"
  (click)="setCurrentAntrag(antrag, i)">
    <th scope="row">{{antrag.id}}</th>
    <td><ngb-highlight [result]="antrag.antragsteller" [term]="filterfcvar.value"></ngb-highlight></td>
    <td>{{antrag.gestelltbei}}</td>
    <td>{{antrag.eingangsdatum}}</td>
    <td>{{antrag.foerderhoehe | currency:'EUR':true}}</td>
    <td>{{antrag.status}}</td>
    <td></td>
  </tr>
  </tbody>
</table>



<div class="list row">
  <div class="col-md-6">
    <div *ngIf="currentAntrag">
      <h4>Antrag</h4>
      <div>
              <label><strong>Nr.</strong></label>
              <pre>{{currentAntrag.id}}</pre>
      </div>
      <div>
        <label><strong>Name:</strong></label>
        <pre>{{currentAntrag.antragsteller}}</pre>
      </div>
      <div>
        <label><strong>Eingangsdatum:</strong></label>
        <pre>{{currentAntrag.eingangsdatum}}</pre>
      </div>
      <div>
        <label><strong>Förderhöhe:</strong></label>
        <pre>{{currentAntrag.foerderhoehe}} {{currentAntrag.foerderhoehewaehrung}}</pre>
      </div>
      <div>
        <label><strong>Gestellt bei:</strong></label>
        <pre>{{currentAntrag.gestelltbei}}</pre>
      </div>
      <div>
        <label><strong>Status:</strong></label>
        <pre>{{currentAntrag.status}}</pre>
      </div>

      <a class="btn btn-warning" routerLink="/antrag/{{currentAntrag.id}}">
        Bearbeiten
      </a>
    </div>

    <div *ngIf="!currentAntrag">
      <br />
      <p>Bitte auf einen Antrag klicken, um Details zu sehen</p>
    </div>
  </div>
</div>

Upvotes: 0

Views: 823

Answers (1)

Panos Boc
Panos Boc

Reputation: 1168

The problem here as you correctly stated is that you are trying to search on an empty stream of data. You can fix that by waiting for the call to get Antraege to finish and the continue with the search observable. Based on you example I would do the following

@Component({
  selector: 'app-antrag-list',
  templateUrl: './antrag-list.component.html',
  styleUrls: ['./antrag-list.component.css'],
  providers: [DecimalPipe]
})
export class AntragListComponent implements OnInit {
  activeModal: NgbActiveModal;

  antraege$: Observable<Antrag[]> = this.antragService.readAll()
    .pipe(tap((antraege) => this.antraege = antraege));
  
  currentAntrag = null;
  currentIndex = -1;
  name = '';

  filteredAntraege$: Observable<Antrag[]>;
  filterfcvar = new FormControl();
  filterfg = this.fb.group({filterfc: this.filterfcvar});;

  constructor(
    private antragService: AntragService,
    public alertService: AlertService,
    public modal: NgbActiveModal,
    private router: Router,
    private modalService: NgbModal,
    pipe: DecimalPipe,
    private fb: FormBuilder
  ) {}

  ngOnInit(): void {
    this.filteredAntraege$ = this.filterfcvar.valueChanges.pipe(
      startWith(''),
      map(text => this.search(text, pipe))
    );
  }

  setCurrentAntrag(antrag, index): void {
    this.currentAntrag = antrag;
    this.currentIndex = index;
  }

  search(text: string, pipe: PipeTransform): Antrag[] {
    console.log('search: text===' + text);
    console.log('search: pipe===' + pipe);
    console.log('antrag filter beispiel: ' + this.antraege);
    return this.antraege.filter(antrag => {
      const term = text.toLowerCase();
      return antrag.antragsteller.toLowerCase().includes(term)
          || antrag.gestelltbei.toLowerCase().includes(term)
          || antrag.status.status.toLowerCase().includes(term);
    });
  }
}

And inside the html template I would use an <ng-container> to wait for the data to load first. Check below:

<table class="table table-striped">
  <thead>
  <tr>
    <th scope="col">id</th>
    <th scope="col">antragsteller</th>
    <th scope="col">gestellt bei</th>
    <th scope="col">Eingangsdatum</th>
    <th scope="col">Förderhöhe</th>
    <th scope="col">Status</th>
    <th scope="col">Aktion</th>
  </tr>
  </thead>
  <tbody>
  <ng-container *ngIf="antraege$ | async">
    <tr *ngFor="let antrag of filteredAntraege$ | async; index as i"  [class.active]="i == currentIndex"
    (click)="setCurrentAntrag(antrag, i)">
      <th scope="row">{{antrag.id}}</th>
      <td><ngb-highlight [result]="antrag.antragsteller" [term]="filterfcvar.value"></ngb-highlight></td>
      <td>{{antrag.gestelltbei}}</td>
      <td>{{antrag.eingangsdatum}}</td>
      <td>{{antrag.foerderhoehe | currency:'EUR':true}}</td>
      <td>{{antrag.status}}</td>
      <td></td>
    </tr>
  </ng-container>
  </tbody>
</table>

You can find a working example here bootstrap table filtering with async data

Upvotes: 1

Related Questions