Search code examples
angularngrx-entity

Angular 18 NGRX 18, how to register a DataService which extends DefaultDataService to override GetAll function, in a standalone configuration?


I created a custom DataService ngrx/entity that extends the NGRX DefaultDataService, I need to get to a custom URL which returns a Json Object. However I haven't been able to register my Custom DataService as I have no modules installed and haven't found any way to register the service so the override never happens. Anyone else having this issue? Installed:

    "@angular/animations": "^18.2.0",
    "@angular/cdk": "^18.2.0",
    "@angular/common": "^18.2.0",
    "@angular/compiler": "^18.2.0",
    "@angular/core": "^18.2.0",
    "@angular/forms": "^18.2.0",
    "@angular/material": "^18.2.0",
    "@angular/platform-browser": "^18.2.0",
    "@angular/platform-browser-dynamic": "^18.2.0",
    "@angular/platform-server": "^18.2.0",
    "@angular/router": "^18.2.0",
    "@angular/ssr": "^18.2.0",
    "@coreui/angular": "^5.2.14",
    "@ngrx/data": "^18.0.2",
    "@ngrx/effects": "^18.0.2",
    "@ngrx/entity": "^18.0.2",
    "@ngrx/router-store": "^18.0.2",
    "@ngrx/store": "^18.0.2",
    "@ngrx/store-devtools": "^18.0.2",
    "express": "^4.18.2",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.14.10"

This is my entity-metadata.ts file:

import {
  EntityMetadataMap,
  EntityDataModuleConfig,
} from '@ngrx/data';
const entityMetadata: EntityMetadataMap = {
  Country: {}
};

const pluralNames = { Country: 'Countries' };

export const entityConfig: EntityDataModuleConfig = {
  entityMetadata,
  pluralNames
};

here is my app.config.ts

import {ApplicationConfig, provideZoneChangeDetection, isDevMode} from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import {provideStore} from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';


import * as fromApp from './store/app.reducer';
import {provideClientHydration} from '@angular/platform-browser';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideHttpClient } from '@angular/common/http';
import { provideStoreDevtools } from '@ngrx/store-devtools';

import {AuthEffects} from "./pages/auth/store/auth.effects";
import { provideRouterStore } from '@ngrx/router-store';
import {provideEntityData, withEffects} from '@ngrx/data';
import {entityConfig} from './entity-metadata';


export const appConfig: ApplicationConfig = {
  providers: [provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideClientHydration(),
    provideAnimationsAsync(),
    provideStore(fromApp.appReducer, { runtimeChecks: {
            strictActionImmutability: true
        } }),
    provideHttpClient(),
    provideEffects(AuthEffects),
    provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() }),
    provideRouterStore(),
    provideEntityData(entityConfig, withEffects())
  ]
};

Even though I have providedIn: 'root' on this DataService it still isn't being invoked. Here is my Custom DataService file:

import { Injectable } from '@angular/core';
import {DefaultDataService, HttpUrlGenerator} from "@ngrx/data";
import {Countries, Country} from "../../../models/countries.model";
import {HttpClient} from "@angular/common/http";
import {map, Observable} from "rxjs";
import {baseUrl} from "../../../../../environments/environments";

@Injectable({
  providedIn: 'root'
})
export class CountryDataService extends DefaultDataService<Country>{

  constructor(http:HttpClient, httpUrlGenerator:HttpUrlGenerator) {
    super('Country', http, httpUrlGenerator);
  }



  override getAll(): Observable<Country[]> {

    return this.http.get<Countries>(baseUrl + '/api/country/GetCountries')
      .pipe(
        map(res => res["COUNTRIES"])
      )
  }
}

my resolver:

import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  Resolve,
  RouterStateSnapshot,
} from '@angular/router';
import { map, Observable } from 'rxjs';
import { CountryEntityService } from './country-entity.service';
import {CountryDataService} from "./country-data.service";
import {CountryActions} from './action-types';

@Injectable({ providedIn: 'root' })
export class CountryResolver implements Resolve<boolean> {
  constructor(
    private countryService: CountryEntityService
  ) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot,
  ): Observable<boolean> {
    return this.countryService.getAll().pipe(map((countries) => !!countries));
  }
}

Solution

  • YOu could register the new service on the AppComponent which is the root of the application.

    @Component({
        ...
      imports: [ ... ],
      providers: [ HeroDataService ] // <-- provide the data service
    }) 
    export class AppComponent {
      constructor(
        entityDataService: EntityDataService,
        heroDataService: HeroDataService,
      ) {
        entityDataService.registerService('Hero', heroDataService); // <-- register it
      }
    }
    

    You could also use the app_initializer method to initialize the service.

    export function initializeApplication(entityDataService: EntityDataService, heroDataService: HeroDataService) {
      return (): void => {
          entityDataService.registerService('Hero', heroDataService); // <-- register it
      });
    }
    
    export const appConfig: ApplicationConfig = {
      providers: [provideZoneChangeDetection({ eventCoalescing: true }),
        provideRouter(routes),
        provideClientHydration(),
        provideAnimationsAsync(),
        provideStore(fromApp.appReducer, { runtimeChecks: {
                strictActionImmutability: true
            } }),
        provideHttpClient(),
        provideEffects(AuthEffects),
        provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() }),
        provideRouterStore(),
        provideEntityData(entityConfig, withEffects()),
        {
          provide: APP_INITIALIZER,
          useFactory: initializeApplication,
          multi: true,
          deps: [EntityDataService, HeroDataService],
        },
      ]
    };