user3241778
user3241778

Reputation: 376

Angular providers lost in extended Component

I need a base component from which my other components can extend. The Parent components have all different services with extend from the same service and I want to pass this services to the Base component, so it can do its work.

Right now I am extending the base component from my Parent component and then try load the service via an injector token which I provide in the Parent component and the parent components module (i just trier both because it didn't work).

But I get an:

    ERROR NullInjectorError: R3InjectorError(fe)[InjectionToken ENTITYLISTSERVICEPROVIDER -> InjectionToken ENTITYLISTSERVICEPROVIDER -> InjectionToken ENTITYLISTSERVICEPROVIDER]: 
   NullInjectorError: No provider for InjectionToken ENTITYLISTSERVICEPROVIDER!

everytime and dont know what to do.

My Parent component looks like this:

@Component({
    selector: 'app-recording-data-entry-form',
    templateUrl: './recording-data-entry-form.component.html',
    styleUrls: ['./recording-data-entry-form.component.scss'],
    providers: [
        { provide: ENTITYLISTSERVICE_PROVIDER, useClass: RecordingsService },
        { provide: ENTITYENDPOINT_PROVIDER, useValue: 'apiEndpoints.RECORDING' },
        { provide: ENTITY_FORM_PATH, useValue: 'RecodingDataEntryFormState.recording' }
    ]
    
})
export class RecordingDataEntryFormComponent extends BaseDataEntryFormComponent<
    RecordingsListModel.RecordingModel,
    RecordingDetailModel.RecordingModel> {
    form: FormGroup;

    @ViewChild('subject_id') subjectIdAutoComplete!: MatAutocompleteTrigger;
    public autoCompletes;
    
    id: number | undefined;
    
    @Select(RecodingDataEntryFormState.getApiData) apiData$!: Observable<any>;
    
    constructor() {
        const form = new FormGroup({
            name: new FormControl({}, [Validators.required]),
            quality_comment: new FormControl({}),
            subject_id: new FormControl({}, [Validators.required]),
            recording_session_id: new FormControl({}, [Validators.required])
        });

        super(form);
        this.form = form;
        this.autoCompletes = this.subjectIdAutoComplete;
    }

My app.module:

//...

declarations: [AppComponent, BaseDataEntryFormComponent],
providers: [
    { provide: HTTP_INTERCEPTORS, useClass: FakeBackend, multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: HttpErrorInterceptor, multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: HttpCredentialsInterceptor, multi: true },
    { provide: 'SnotifyToastConfig', useValue: notificationConfig },
    { provide: LOCALE_ID, useValue: 'de-DE' },
    SnotifyService
],
bootstrap: [AppComponent]
//...
export class AppModule {
    constructor(private injector: Injector) {
        ServiceLocator.injector = this.injector;
    }
}
//...

And the base Component:

@Component({
    selector: 'app-base-data-entry-form-component',
    template: '',
    styles: []
})
export class BaseDataEntryFormComponent<L extends DatatableData, D> {
    public options: { [key: string]: any[] } = {};

    id: number | undefined;
    private readonly dataEntryService: DataEntryService;
    private baseService: BaseService;
    private store: Store;
    private router: Router;

    private baseDetailsPageService: BaseDetailsPageService<RecordingDetailModel.RecordingModel>;
    private basePageService: BasePageService<L>;
    private formPath: string;
    private activeEndpoint: string = '';
    @SelectSnapshot(RecodingDataEntryFormState.formState) formState!: 'edit' | 'create';

    @SelectSnapshot(RecodingDataEntryFormState.dirty) isDirty!: boolean;

    public constructor(protected form: FormGroup) {
        this.baseService = ServiceLocator.injector.get(BaseService);
        this.store = ServiceLocator.injector.get(Store);

        this.router = ServiceLocator.injector.get(Router);

        this.baseDetailsPageService = ServiceLocator.injector.get(RecordingDetailsService);
        this.basePageService = ServiceLocator.injector.get<BasePageService<L>>(ENTITYLISTSERVICE_PROVIDER);
        this.formPath = ServiceLocator.injector.get<string>(ENTITY_FORM_PATH);

        this.activeEndpoint = ServiceLocator.injector.get<string>(ENTITYENDPOINT_PROVIDER);

        const formState = this.dataEntryService.getFormState();
        this.store.dispatch(new RecordingFormActions.UpdateFormState(formState));

        this.makeApiCall(this.dataEntryService);

I think that because the RecordingDataEntryFormComponent extends the BaseDataEntryFormComponent, the BaseDataEntryFormComponent should have the same providers, but it doesn't seem so.

Any help is very appreciated

Upvotes: 1

Views: 626

Answers (2)

user3241778
user3241778

Reputation: 376

I solved it by not using my ServiceLocator anymore and instead inject the "Injector" locally at the RecordingDataEntryFormComponent and then passed it to the BaseDataEntryFormComponent.

Upvotes: 0

Aakash Garg
Aakash Garg

Reputation: 10979

You can pass the instance of the token from RecordingDataEntryFormComponent to BaseDataEntryFormComponent while calling super. Plus BaseDataEntryFormComponent shouldn't be a component. It should be a normal class. A component shouldn't extend another component as it won't serve a benefit. you can put @Directive() on it to use @Input @Output type decorators.

Upvotes: 1

Related Questions