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?
3 Réponses :
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(), ) } }
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.
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";
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.
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')),
);