r3plica
r3plica

Reputation: 13367

Angular 8.2 Shared component that extends another component throws No component factory error

I am currently doing some refactoring on an application, namely some forms. I noticed they were all similar. Before the refactor they were working, but when I added the new parent class and extended it, I started getting this error for the child components:

ERROR Error: No component factory found for SaveCategoryComponent. Did you add it to @NgModule.entryComponents?

My parent component looks like this:

import { OnInit, Inject } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { BaseModel, Attempt } from '@models';
import { NotificationService } from 'src/app/_shared/notification/notification.service';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { Observable } from 'rxjs';

export class SaveComponent implements OnInit {
    public formGroup: FormGroup;
    public submitted: boolean;
    public notifications: object;
    public isEditing: boolean;

    // convenience getter for easy access to form fields
    get f() {
        return this.formGroup.controls;
    }

    constructor(
        @Inject(MAT_DIALOG_DATA) public model: BaseModel,
        public dialogRef: MatDialogRef<any>,
        public notificationService: NotificationService,
    ) {}

    ngOnInit(): void {
        this.isEditing = !!this.model.id;
    }

    public onSave(callback: (model: any) => Observable<any>) {
        this.submitted = true;

        if (this.formGroup.valid) {
            callback(this.formGroup.value).subscribe(
                (response: Attempt<BaseModel>) => {
                    if (response.failure) {
                        this.notificationService.show(`${response.error.message}`, 'danger');
                    } else {
                        this.notificationService.show(`Successfully saved your category.`, 'success');
                        this.formGroup.reset();
                    }

                    this.submitted = false;
                    this.dialogRef.close(response.result);
                },
                () => {
                    this.submitted = false;
                },
            );
        }
    }
}

As you can see, it is looking for some data which in this case is BaseModel:

export interface BaseModel {
    id: string | number;
}

And my category looks like this:

import { BaseModel } from './base-model';

export class Category implements BaseModel {
    id: string;
    name: string;
    image: string;
    active: boolean;
}

Those all compile and look fine. Then I have the child which looks like this:

import { Component, OnInit, Inject } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { SaveComponent } from '../save.component';

import { Category } from '@models';
import { CategoryService } from '@services';
import { NotificationService } from '../../notification/notification.service';

@Component({
    selector: 'app-save-category',
    templateUrl: './save-category.component.html',
    styleUrls: ['./save-category.component.scss'],
})
export class SaveCategoryComponent extends SaveComponent implements OnInit {
    constructor(
        @Inject(MAT_DIALOG_DATA) public model: Category,
        public dialogRef: MatDialogRef<SaveCategoryComponent>,
        public notificationService: NotificationService,

        private formBuilder: FormBuilder,
        private categoryService: CategoryService,
    ) {
        super(model, dialogRef, notificationService);
    }

    ngOnInit(): void {
        this.formGroup = this.formBuilder.group({
            id: [this.model.id, Validators.required],
            name: [this.model.name, Validators.required],
            image: [this.model.image],
            active: [this.model.active],
        });
        super.ngOnInit();
    }

    public save() {
        const method = this.isEditing ? 'update' : 'create';
        this.onSave(this.categoryService[method]);
    }
}

This is part of my shared module, and is declared, exported and added as an entryComponent:

@NgModule({
    imports: [
        CommonModule,
        ReactiveFormsModule,
        RouterModule,
        FormsModule,           

        MatAutocompleteModule,
        MatButtonModule,
        MatCardModule,
        MatDialogModule,
        MatFormFieldModule,
        MatIconModule,
        MatInputModule,
        MatRadioModule,
    ],
    declarations: [
        AlertDialogComponent,
        ConfirmationDialogComponent,
        SaveBrandComponent,
        SaveCategoryComponent,
    ],
    exports: [
        AlertDialogComponent,
        ConfirmationDialogComponent,
        SaveBrandComponent,
        SaveCategoryComponent,
    ],
    providers: [DecimalPipe],
    entryComponents: [
        NotificationComponent,
        UploadImagesComponent,

        AlertDialogComponent,
        ConfirmationDialogComponent,
        SaveBrandComponent,
        SaveCategoryComponent,
    ],
})
export class SharedModule {}

(I have removed any code from the module that isn't related at all) I also have a dialog service I created (which was working before I created the parent:

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

import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog';

@Injectable({
    providedIn: 'root',
})
export class DialogService {
    constructor(private dialog: MatDialog) {}

    public open(component: any, model: any): MatDialogRef<any> {
        const dialogConfig = new MatDialogConfig();

        dialogConfig.disableClose = true;
        dialogConfig.autoFocus = true;
        dialogConfig.data = model;

        return this.dialog.open(component, dialogConfig);
    }
}

And then in my main component, I do something like this:

openEditModal(model: Category) {
    const modalRef = this.dialogService.open(SaveCategoryComponent, model);

    modalRef.afterClosed().subscribe((result: Category) => {
        if (result) {
            this.updateItem(result);
            this.notificationSvc.show('You have successfully updated ' + result.name);
        }
    });
}

All this was working before I created the SaveComponent. As soon as I try to extend it, I get the error above. I know the component is defined in entryComponents in the shared module, so I have no idea what it's complaining about.

Can anyone help?

Upvotes: 1

Views: 530

Answers (1)

r3plica
r3plica

Reputation: 13367

I spent hours on this and could not fix it. Then I found a post that mentioned that entryComponents are not used in Angular 9, so I updated all my packages following this guide:

https://update.angular.io/

When I did this, I started getting new errors. I went through it all and eventually I found that my AppModule required both FormsModule and ReactiveFormsModule to be imported. When I did this, everything started working.

In the interest of science, I decided to go back to my original branch and try importing those modules to see if that fixed the issue in 8.2. It did not, so I have no idea how to fix it there. The only solution I found was upgrading :(

Upvotes: 1

Related Questions