'Format data in store with @ngrx/data and @ngrx/entity, not in component (subscriber)
While working with ngrx/data
I find myself parsing and formatting data from the store in multiple places: converting strings to date objects or formatting strings. I recently discovered filterFn
which lets me fetch only the entities that are relevant for my use case from the store, but I can't seem to find anything which lets me apply formatting to all of the entities of a certain kind.
Here is an example where I have to format and parse my data in my component before subscribing to it. Ideally this should not be done here, but in a more central place...any ideas of how to deal with this issue in ngrx?
entity.data.config.ts
// I would like to parse and format data here. Is that a good idea?
const entityMetadata: EntityMetadataMap = {
letter: {
// https://ngrx.io/guide/data/entity-metadata#filterfn
filterFn: (entities, clientId) => {
return entities.filter(entity => entity.clientId === Number(clientId));
},
},
};
my.component.ts
@Component({
selector: 'my-component',
templateUrl: './my.component.html',
})
export class MyComponent implements OnInit {
public letters$: Observable<LetterParsed[]>;
public clientId: string;
public constructor(public letterDataService: LetterDataService) {}
public ngOnInit(): void {
this.clientId = '1';
// call API, GET letter voor current clientId and add to store
this.letterDataService.getWithQuery({
clientId: this.clientId,
});
// set filter and fetch only filtered letter entities from store
this.letterDataService.setFilter(this.clientId);
this.letters$ = this.letterDataService.filteredEntities$.pipe(
// this is where I do the parsing, which ideally I want to avoid...
map((letters: Letter[]) => letters.map(letter => this.parseLetter(letter))),
);
}
private parseLetter(letter: Letter): LetterParsed {
return {
...letter,
status: this.formatStatus(letter.status),
date: new Date(letter.date),
};
}
/**
* @param status e.g 'I_LOVE_ANGULAR'
* @returns 'I love angular'
*/
private formatStatus(status: string): string {
const splitLowercase = status.split('_').join(' ').toLowerCase();
return splitLowercase[0].toUpperCase() + splitLowercase.slice(1);
}
}
Solution 1:[1]
You can extend the default DataService to write a custom data service and format your entities there. That way, you'll only need to format them once per entity type.
Refer to the documentation here: https://ngrx.io/guide/data/entity-dataservice#custom-entitydataservice
Here's how you might do this for your use case:
@Injectable()
export class LetterDataService extends DefaultDataService<Letter> {
constructor(
http: HttpClient,
httpUrlGenerator: HttpUrlGenerator,
logger: Logger
) {
super("Letter", http, httpUrlGenerator);
}
getWithQuery(params: string | QueryParams): Observable<Letter[]> {
return super
.getWithQuery(params)
.pipe(
map((entities) => entities.map((entity) => this.mapEntity(entity)))
);
}
private mapEntity(entity: Letter): Letter {
return {
...entity,
status: this.formatStatus(letter.status),
date: new Date(letter.date),
};
}
/**
* @param status e.g 'I_LOVE_ANGULAR'
* @returns 'I love angular'
*/
private formatStatus(status: string): string {
const splitLowercase = status.split("_").join(" ").toLowerCase();
return splitLowercase[0].toUpperCase() + splitLowercase.slice(1);
}
}
You'll also need to register this custom data service, like so:
import { EntityDataService } from '@ngrx/data'; // <-- import the NgRx Data data service registry
import { LetterDataService } from './letter-data-service';
@NgModule({
imports: [ ... ],
providers: [ LetterDataService ] // <-- provide the data service
})
export class EntityStoreModule {
constructor(
entityDataService: EntityDataService,
letterDataService: LetterDataService,
) {
entityDataService.registerService('Letter', letterDataService); // <-- register it
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Sourav |