0

I'm learning about observables atm and I have a question. I get a json array from my backend api and put it in an Observable (where image is my own defined model). Now these are all the images but I only want those whose name starts with display. I've tried a few things but I can't find the solution. This is what I've got so far.

  get images$(): Observable<Image[]> {
    return this._fetchImages$
      .pipe(
        filter(i => i.filter(img => img.name.toLowerCase().startsWith("display")))
      );
  }

this gives: Type 'Image[]' is not assignable to type 'boolean'.ts(2322) filter.d.ts(3, 46): The expected type comes from the return type of this signature.

Image model class

export class Image {
  constructor(
    private _name: string,
    private _iso: string,
    private _shutter: string,
    private _aperture: string,
    private _country: string,
    private _likes: number,
    private _content: string
  ) {}

  get name(): string {
    return this._name;
  }

  get iso(): string {
    return this._iso;
  }

  get shutter(): string {
    return this._shutter;
  }

  get aperture(): string {
    return this._aperture;
  }

  get country(): string {
    return this._country;
  }

  get likes(): number {
    return this._likes;
  }

  get content(): string {
    return this._content;
  }

  set likes(value: number) {
    this._likes = value;
  }

  static fromJson(json: any): Image {
    return new Image(
      json.name,
      json.iso,
      json.shutterSpeed,
      json.aperture,
      json.country,
      json.likes,
      json.content
    );
  }
}

The service that provides me with the values

export class ImageDataService {
  constructor(private http: HttpClient) {}

  get images$(): Observable<Image[]> {
    return this.http
      .get(`${environment.apiUrl}/images/`)
      .pipe(map((list: any[]): Image[] => list.map(Image.fromJson)));
  }
}

the component that asks for the observable

export class GridComponent implements OnInit {
  public countryFilter: string;
  public filterImage$ = new Subject<string>();
  private _fetchImages$: Observable<Image[]> = this._imageDataService.images$;

  constructor(private _imageDataService: ImageDataService) {
    this.filterImage$
      .pipe(
        distinctUntilChanged(),
        debounceTime(400),
        map(val => val.toLowerCase())
      )
      .subscribe(val => (this.countryFilter = val));
  }

  get images$(): Observable<Image[]> {
    return this._fetchImages$.pipe(
      map(i => i.filter(img => img.name.toLowerCase().startsWith('display')))
    );
  }

  ngOnInit() {}
}
<div>
  <mat-form-field>
    <input matInput placeholder="Country" type="text" #countryName (keyup)="filterImage$.next($event.target.value)"
      class="browser-default">
  </mat-form-field>
  <mat-grid-list cols="3" gutterSize=" 5px" rowHeight="500px">
    <mat-grid-tile *ngFor="let image of (images$ | async)">
      <app-image [image]="image"></app-image>
    </mat-grid-tile>
  </mat-grid-list>
</div>

export class ImageComponent implements OnInit {
  private _icon: string;
  @Input('image') public image: Image;

  constructor() {
    this._icon = 'favorite_border';
  }

  ngOnInit() {}

  like() {
    if (this._icon === 'favorite_border') {
      this._icon = 'favorite';
      this.likes++;
    } else {
      this._icon = 'favorite_border';
      this.image.likes--;
    }
    console.log(this._icon);
  }

  get icon(): string {
    return this._icon;
  }

  set icon(value: string) {
    this._icon = value;
  }

  get iso(): string {
    return this.image.iso;
  }

  get aperture(): string {
    return this.image.aperture;
  }

  get shutterspeed(): string {
    return this.image.shutter;
  }

  get country(): string {
    return this.image.country;
  }

  get name(): string {
    return this.image.name;
  }

  get content(): string {
    return this.image.content;
  }

  get likes(): number {
    return this.image.likes;
  }

  set likes(value: number) {
    this.image.likes = value;
  }
}

I get 10 json objects sent to me:

{
  "id": 1,
  "name": "Header",
  "iso": "ISO-200",
  "shutterSpeed": "1/80 sec",
  "aperture": "f/5.6",
  "country": "Belgium",
  "content": //a base64 string
}
{
  "id": 2,
  "name": "Parallax1",
  "iso": "ISO-100",
  "shutterSpeed": "1/200 sec",
  "aperture": "f/10",
  "country": "Italy",
  "content": another base64 string
}
{
  "id": 5,
  "name": "Display1",
  "iso": "ISO-100",
  "shutterSpeed": "1/200 sec",
  "aperture": "f/10",
  "country": "Italy",
  "content": another base64 string
}

Now the major difference between the images is the name: I've got 1 header, 3 Parallaxes and 6 Display images. I now want to filter that whay I get only the Display images. Basically: 10 images come in ---> 6 come out

Kind regards

Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
Mout Pessemier
  • 1,665
  • 3
  • 19
  • 33
  • You want to transform each event emitted by the observable (the event is an array), into a different event (a filtered array). So you need to use the `map` operator, not the `filter`operator. – JB Nizet Mar 25 '19 at 16:22
  • 1
    Possible duplicate of [Simple filter on array of RXJS Observable](https://stackoverflow.com/questions/37991713/simple-filter-on-array-of-rxjs-observable) – ConnorsFan Mar 25 '19 at 16:22
  • @JBNizet I've tried using a map but when I do, 0 images show – Mout Pessemier Mar 25 '19 at 16:24
  • Using the wrong operator won't fix your bug. it will just make it worse, or different. Post a complete minimal example reproducing the problem. – JB Nizet Mar 25 '19 at 16:25
  • If no image shows, then it probably means that the template of the image component has a problem. That said, you have a lot of useless boilerplate. All of your getters in the image component are useless, since you expose the image anyway. The Image class could be replaced by a simple interface, and no map() would be necessary. The images$ getter recreates a new observable at each change detection, for nothing. – JB Nizet Mar 25 '19 at 16:46
  • Please post a complete minimal example reproducing the problem for others to take a look (See StackBlitz.com) – nircraft Mar 25 '19 at 18:07

2 Answers2

0

I think you should change filter for map, like this:

.pipe(
    map(
        i => i.filter(
            img => img.name.toLowerCase().startsWith("display")
        )
    )
);
German Burgardt
  • 375
  • 1
  • 5
0

I've moved the mapping to the class where I get them using a HttpClient instead of trying to filter them in my component. It works without querystring now.

code:

export class ImageDataService {
  constructor(private http: HttpClient) {}

  get images$(): Observable<Image[]> {
    return this.http
      .get(`${environment.apiUrl}/images/`)
      .pipe(
        map((list: any[]): Image[] => list.map(Image.fromJson)),
        map(imgs =>
          imgs.filter(img => img.name.toLowerCase().startsWith('display'))
        )
      );
  }
}
Mout Pessemier
  • 1,665
  • 3
  • 19
  • 33