1
votes

Groupby Observable Angulaire 7

J'essaie de regrouper les éléments du tableau en fonction de la valeur que je récupère de l'observable et d'afficher ces groupes sous la forme d'une différente sur la vue.

J'ai un module web api en mémoire qui contient une classe de l'exemple simplifié ci-dessous.

J'apprends toujours Angular alors veuillez excuser ma noobness

Ex: in-memory-data.service.ts

<h1>My Access</h1>
<app-app-search></app-app-search>

<div fxLayout="row wrap">
  <div *ngFor="let app of apps" fxFlex.gt-sm="25" fxFlex.gt-xs="50" fxFlex="100">

    <a routerLink="/detail/{{app.id}}">
      <mat-card class="card-widget mat-teal">
        <div mat-card-widget>
          <div mat-card-float-icon>
            <mat-icon>error</mat-icon>
          </div>
          <div class="pl-0">
            <h2 mat-card-widget-title>{{app.app}}</h2>
          </div>
        </div>
      </mat-card>
    </a>

  </div>
</div> 

Pour chacune des différentes valeurs d'application, une nouvelle doit être créée, pas pour chaque valeur d'application, c'est exactement ce qu'elle fait actuellement.

Voici mon code.

applications.service.ts MODIFIÉ:

import { Component, OnInit } from '@angular/core';
import { Apps } from '../../applications'
import { APPS } from '../../mock-applications';
import { ApplicationsService } from 'src/app/applications.service';

export class HomeComponent implements OnInit {

  apps: Apps[];

  constructor(private appService: ApplicationsService) { }

  ngOnInit() {
    this.getApps();
  }

  getApps(): void{
    this.appService.getApps()
    .subscribe(apps =>  this.apps = apps);
  }

}

home.component.ts

import { Injectable } from '@angular/core';
import { Apps } from './applications';
import { APPS } from './mock-applications';
import { Observable, of } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { MessagesService } from './messages.service';
import { catchError, map, switchMap, tap, groupBy, mergeMap } from 'rxjs/operators';

export class ApplicationsService {

private applicationUrl = 'api/apps';

constructor(private messageService: MessagesService, private http: HttpClient ) { }

getApps(): Observable<Apps[]> {
  return this.http.get<Apps[]>(this.applicationUrl)
    .pipe(
      groupBy(application=> application.app),
      mergeMap(obs => {
        retun obs.pipe(
          toArray(),
          map(apps => {
            return {id: obs.key, group: apps}
          })
        )
      }),
      toArray(),
      tap(_ => this.log('fetched apps')),
      catchError(this.handleError<Apps[]>('getApps', []))
    )
}

/*Type 'Observable<Apps[] | {}[]>' is not assignable to type 'Observable<Apps[]>'.
  Type 'Apps[] | {}[]' is not assignable to type 'Apps[]'.
    Type '{}[]' is not assignable to type 'Apps[]'.
      Type '{}' is missing the following properties from type 'Apps': guid, mots_spi_indicator, mots_sox_indicator, mots_pci_indicator, and 42 more./*

getApp(id: string): Observable<Apps> {
  const url = `${this.applicationUrl}/${id}`;
  return this.http.get<Apps>(url).pipe(
    tap(_ => this.log(`fetched app guid=${id}`)),
    catchError(this.handleError<Apps>(`getApp guid=${id}`))
  );

}
}

home.component.html

export class InMemoryDataService implements InMemoryDbService {
createDb(){
const apps = [
{id: '1', app: 'app1', env_id:2, env:'DEV'},
{id: '2', app: 'app2' env_id:2, env:'DEV'},
{id: '4', app: 'app3' env_id:2, env:'DEV'},
{id: '5', app: 'app1' env_id:3, env:'Test'},
{id: '6', app: 'app2' env_id:3, env:'Test'},
{id: '7', app: 'app3' env_id:1, env:'PROD'},
];
return {apps}
}

Comment puis-je faire en sorte que chaque nom d'application soit placé dans son propre groupe?


0 commentaires

3 Réponses :


3
votes

Voici une façon de grouper:

app1
  { "id": "1", "app": "app1", "env_id": 2, "env": "DEV" }
  { "id": "5", "app": "app1", "env_id": 3, "env": "Test" }
app2
  { "id": "2", "app": "app2", "env_id": 2, "env": "DEV" }
  { "id": "6", "app": "app2", "env_id": 3, "env": "Test" }
app3
  { "id": "4", "app": "app3", "env_id": 2, "env": "DEV" }
  { "id": "7", "app": "app3", "env_id": 1, "env": "PROD" }
<div *ngFor="let app of apps$ | async">
  <h4>{{ app.id }}</h4>
  <ul>
    <li *ngFor="let item of app.group">{{ item | json }}</li>
  </ul>
</div>

Résultat:

export class AppComponent implements OnInit {
  apps$: Observable<any>;
  createDb() {
    const apps = [
      { id: '1', app: 'app1', env_id: 2, env: 'DEV' },
      { id: '2', app: 'app2', env_id: 2, env: 'DEV' },
      { id: '4', app: 'app3', env_id: 2, env: 'DEV' },
      { id: '5', app: 'app1', env_id: 3, env: 'Test' },
      { id: '6', app: 'app2', env_id: 3, env: 'Test' },
      { id: '7', app: 'app3', env_id: 1, env: 'PROD' },
    ];
    return from(apps)
  }

  ngOnInit() {
    this.apps$  = this.createDb().pipe(
      // Tell rx which property to group by
      groupBy(application => application.app),
      mergeMap(obs => {
        // groupBy returns a GroupedObservable, so we need to expand that out
        return obs.pipe(
          toArray(), 
          map(apps => {
            return {id: obs.key, group: apps}
          })
        )
      }),
      toArray(),
    )
  }
}

Stackblitz


9 commentaires

Cela ne fonctionnerait-il pas uniquement s'il s'agissait de son propre composant? Il est actuellement utilisé pour fournir un faux back-end car pour le moment, les données ne sont pas extraites d'un serveur. J'ai un autre service qui renvoie une observable des applications. C'est là que je voudrais répartir les candidatures en groupes. Je vais essayer de pippyback hors de votre pipe () là-bas et voir comment ça se passe


Pour cet exemple, oui, mais je ne vais pas recréer un service en mémoire, installer du matériel, etc. pour montrer la logique rxjs :)


Okay gotcha, sortant de votre logique rxjs, j'ai ajouté les fonctions groupBy et mergeMap comme votre exemple ci-dessous, mais j'obtiens cette erreur, voir édité ci-dessus. J'ai ajouté un commentaire qui contient le message d'erreur que je vois


La fonction getApps est saisie sous la forme Observable , mais elle renvoie maintenant un type de Observable <{id: string, group: Apps []} []> .


Peut-être que je manque quelque chose. getApps (): Observable <{id: string, group: Apps []} []> {toujours tous soulignés en rouge :(


Je sais pas mec, j'ai mis à jour le stackblitz avec mes typages et c'est bien là. J'imagine que votre interface App est différente.


Vous pouvez en savoir plus sur groupBy dans mon article: blog. angularindepth.com/...


@thomi cela fonctionne sur le blitz, pouvez-vous préciser "ne fonctionne pas"


Oui je peux. L'opérateur toArray () de la documentation ne se déclenche que lorsque l'observable se termine. Dans les cas où vous souhaitez surveiller une socket ou une base de données en temps réel, ce n'est pratiquement jamais et vous n'obtiendrez pas de données ou de messages d'erreur d'ailleurs ... Mon cas d'utilisation, par exemple est de récupérer les données d'une base de données et de les préparer dans la vue pour la présentation, et de les faire trier et modifier en temps réel. J'ai fini par le mapper à la structure dont j'avais besoin avec un bon vieux lodash. Après plusieurs heures d'enquête, c'était un jeu d'enfant. Si vous voulez voir mon code, je le partagerai avec plaisir dans une réponse.



2
votes

utilisez lodash pour cela

getApps(): void{
    this.appService.getApps()
    .subscribe(apps => {
       var grouppedApps = groupBy(apps,"app");
       // You can do whatever you want now
     } );
  }

...

npm install lodash

import * as groupBy from "lodash/groupBy";


2 commentaires

Merci, laissez-moi essayer


Ok, c'est parfait. Je l'ai fait et j'ai pu consigner la sortie de la console et elle a été divisée par applications comme prévu. Alors maintenant, comment puis-je afficher cela dans la vue en utilisant le modèle html ci-dessus? J'ai essayé de jouer avec certaines des valeurs, mais la page d'accueil revient vide sans applications.



0
votes

L'opérateur from () crée une observable qui f arrête les éléments du tableau puis se termine . L'opérateur toArray () collecte toutes les émissions sources et les émet comme un tableau lorsque la source est terminée . Donc, pour un cas d'utilisation où vous ne disposez pas d'un ensemble fixe de valeurs à émettre, vous ne pouvez pas utiliser toArray () code >. Vous pouvez cependant utiliser map avec _.groupBy () depuis lodash . Voici ce que j'ai fait pour ce cas particulier:

  this.filteredMeetingList$ = this.sorting$.pipe(
  switchMap(sorting => this.meetingList$.pipe(
    // ...
    map(sMeetings => sMeetings.map(m => ({
      ...m,
      day: moment(m.startDateTime).format('DD. MMM'),
    }))),
  )),
  map(elem => _.groupBy(elem, 'day')),
 );


0 commentaires