0

So in my Angular project, I was trying to have an abstract base class for editing forms. Let's call it Editor.

There will be many components that do operations on a form so they inherit from the base class. For example, there is a component which edits a user. So the component would look like this

export abstract class Editor {
  public form!: FormGroup;
  private _hasChanges = false;

  constructor() {
    this.createForm();
    this.subscribe();
  }

  protected abstract createForm(): void;

  private subscribe(): void {
    this.form.valueChanges.subscribe((_) => {
      this._hasChanges = true;
    });
  }

  public get hasChanges(): boolean {
    return this._hasChanges;
  }
}

@Component({
  selector: 'user-editor',
  templateUrl: './user-editor.component.html',
  styleUrls: ['./user-editor.component.scss'],
})
export class UserEditorComponent extends Editor {
  public dummy = 'dummyText';

  constructor(private fb: FormBuilder) {
    super();
  }

  protected createForm(): void {
    this.form = this.fb.group(
      name: ['', Validators.required]
    );
  }
}

But this sample won't work! in the implementatoin of createForm, this.fb will be undefined. Even more, when putting a breakpoint and inspecting this. It seems like we are not in the chlid component but in the parent component.

enter image description here

Since the 'dummy' variable is not there.

I made a minimal reproducible sample here

Wouter Vandenputte
  • 1,948
  • 4
  • 26
  • 50

1 Answers1

0

Since you have never passed the FormBuilder dependency to your parent class, it's undefined in it's scope.

In your UserEditorComponent's constructor you have to call the parent class by the super() method, which instantiates your parent class then applies everything provided by your child class.

If you modify your baseClass to accept parameters:

// Editor
constructor(private fb: FormBuilder) {
  this.createForm();
  this.subscribe();
}

And your child's constructor:

// UserEditorComponent
constructor(private fb: FormBuilder) {
  super(fb);
}

Then you can pass your dependency.

Alternatively (this is not tested), but if you would call the init methods in your child instead of the base class, it also should work:

// UserEditorComponent
constructor(private fb: FormBuilder) {
  super();
  this.createForm();
  this.subscribe();
}

Because you have already initialzed your base class and the dependencies are available in your child class. Although, this is highly impractical.

A more sophisticated solution would be the use of Angular's injector. If you have multiple dependencies, maybe it would be worth it to pass only the injector into your base class and cherry-pick what you need there.

There's an explanation about the injector here: https://stackoverflow.com/a/41433007/5627312

ouflak
  • 2,458
  • 10
  • 44
  • 49
Roland Bobek
  • 21
  • 1
  • 2