1
votes

I have a problem where my application giving an error: No Provider for Data Service.

This typically happens when you have forgotten to list your service as a provider either in the app module or the feature module, however in my case I have set it as a provider.

My application is built on Angular 9 with Ngrx statement management and interacting with a Firestore DB.

I am not doing a simple provider as i am trying to tie the service into ngrx/data which is really designed to work with a Rest API which firebase is not.

Error in browser * NullInjectorError: R3InjectorError(ProductsModule)[ProductsResolver -> DataService -> DataService -> DataService -> DataService -> DataService]: NullInjectorError: No provider for DataService! *

Code is below if anyone can see something obvious or suggest something to point me in the right direction.

Product Feature Module I am setting the Data Service as a provider in the feature module - I have also tried moving up to App Module but still getting the same error

import { ProductsResolver } from "./services/products.resolver";
import { ProductsListComponent } from "./products-list/products-list.component";
import { ProductComponent } from "./product/product.component";
import { DataService } from "./services/generic-data-service";
import { Product } from "@shared/models/product.model";

import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";

import {
    EntityMetadataMap,
    EntityCollectionServiceElementsFactory
} from "@ngrx/data";
import { FirestoreService } from "@core/services/firestore.service";


const routes: Routes = [
    {
        path: "products-list",
        component: ProductsListComponent,
        resolve: {
            products: ProductsResolver
        }
    }
];

@NgModule({
    declarations: [ProductsListComponent, ProductComponent],
    imports: [
        RouterModule.forChild(routes)
    ],
    providers: [
        {
            provide: "ProductsService",
            useFactory: (
                firestoreService: FirestoreService,
                elementsFactory: EntityCollectionServiceElementsFactory
            ) => {
                return new DataService<Product>(
                    Product,
                    firestoreService,
                    elementsFactory
                );
            },
            deps: [FirestoreService, EntityCollectionServiceElementsFactory],
            multi: false
        },
        ProductsResolver
    ]
})
export class ProductsModule {}

Product List Component I am injecting the service in the component but get an error in the browser saying there is no provider.

import { DataService } from "../services/generic-data-service";
import { Product } from "@shared/models/product.model";

import {
    ChangeDetectionStrategy,
    Component,
    OnInit,
    ViewEncapsulation,
    Inject
} from "@angular/core";

import { Observable } from "rxjs";

@Component({
    selector: "fw-products",
    templateUrl: "./products-list.component.html",
    styleUrls: ["./products-list.component.scss"],
    encapsulation: ViewEncapsulation.None,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductsListComponent implements OnInit {
    products$: Observable<any>;

    constructor(
        @Inject("ProductsService")
        private _dataService: DataService<Product>
    ) {}


    ngOnInit(): void {
        this._dataService.getMyEntities().subscribe(data => {
            this._dataService.clearCache();
            this._dataService.upsertManyInCache(data);
            this._dataService.entities$.subscribe(
                data => console.log(data)
            );
        });
    }

}

Data Service

import { FirestoreService } from "@core/services/firestore.service";
import { Product } from "@shared/models/product.model";
import { BaseModel } from "@shared/models/entity-base.model";

import { Injectable } from "@angular/core";

import {
    EntityCollectionServiceBase,
    EntityCollectionServiceElementsFactory
} from "@ngrx/data";
import { Observable } from "rxjs";

Injectable();
export class DataService<
    T extends BaseModel
> extends EntityCollectionServiceBase<T> {
    x: string;

    constructor(
        cls: typeof Product,
        public firestoreService: FirestoreService,
        elementsFactory: EntityCollectionServiceElementsFactory
    ) {
        super(cls.entityName, elementsFactory);
        this.x = cls.firebasePath;
    }

    getMyEntities(): Observable<any[]> {
        return this.firestoreService.getAll(this.x);
    }

    createEntity(data: any): void {
        this.firestoreService.createDoc(`${this.x}/`, data);
    }

    updateEntity(data: any): void {
        this.firestoreService.updateDoc(`${this.x}/`, data);
    }

    deleteEntity(data: any): void {
        this.firestoreService.deleteDoc(`${this.x}/`, data);
    }
}

Products Resolver

import { Injectable } from "@angular/core";
import {
    Resolve,
    ActivatedRouteSnapshot,
    RouterStateSnapshot
} from "@angular/router";
import { Observable } from "rxjs";
import { map, tap, filter, first } from "rxjs/operators";
import { DataService } from './generic-data-service';
import { Product } from '@shared/models/product.model';

@Injectable()
export class ProductsResolver implements Resolve<boolean> {
    constructor(private _productsService: DataService<Product>) {}

    resolve(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
    ): Observable<boolean> {
        return this._productsService.loaded$.pipe(
            tap(loaded => {
                if (!loaded) {
                    this._productsService.getAll();
                }
            }),
            filter(loaded => !!loaded),
            first()
        );
    }
}
2
Needs to be @Injectable() on your DataService not just Injectable()Andrew Allen
Thanks but that does not seem to make any difference.ccocker
Error starts ProductsResolver -> DataService. What does ProductsResolver constructor look like?Andrew Allen
You are absolutely right, I took the resolver out of the route and it works. So my issue is in the resolver. I have added the resolver code above. Thank you i have been looking in the wrong placeccocker
Yes shall do i have just been testing, it all seems to be working will write up an answer. Thank youccocker

2 Answers

1
votes

The issue was in my resolver, I was calling the wrong method of .getAll() which uses the default behaviour (http call to /api). What i should have been using is the GetMyEntities() method which is overridden to make a call to firestore to load the data.

I would also have needed to update the Resolver to Inject("ProductService") in its constructor.

I could not just use the {providedIn: root} because I am overriding the default behavior (http call to /api) of @ngrx/data to instead call Firestore (AngularFire).

For now I have just removed the Resolver as all it was doing is making sure data was loaded before it loaded component and that only takes a second on first initial load, after that the data is in the store and load is instant.

The solution above works well now as it is using @ngrx/data with firestore and virtually eliminates all the boilerplate code normally required with @ngrx to write actions, reducers, selectors etc.

1
votes

Try annotating your service class with @Injectable({providedIn: 'root'})