10

How can I share dependency injection between child and parent components with the new Angular 2.3 Component Inheritance.

e.g. I want to move AlertService down into the parent component and leave TraingCompanyService in the derived component

Current Component

@Component({
    selector: 'wk-training-company-edit',
    template: require('./edit.html')
})
export class TrainingCompanyEditComponent implements OnInit, OnDestroy {

    constructor(
                private alert: AlertService,
                private trainingCompanyService: TrainingCompanyService
                ) {

    }
}

Refactored Components (V1)

Super must be called before calling this in the constructor of the derived class

@Component({
    selector: 'wk-training-company-edit',
    template: require('./edit.html')
})
export class TrainingCompanyEditComponent extends BaseAdminEditComponent implements OnInit, OnDestroy {

    constructor(
                private alert: AlertService,
                private trainingCompanyService: TrainingCompanyService
                ) {

        // Error: Super must be called before calling this in the constructor of the derived class
        super(this.alert);
    }
}

export class BaseAdminEditComponent {

    constructor(private alert: AlertService) {
    }

    protected handleSaveError(error: any) {

        if (error.message) {
            if (error.errors && _.isArray(error.errors) && error.errors.length > 0) {
                this.alert.error(_.join(error.errors, '\n'), error.message);
            }
            else {
                this.alert.error(error.message);
            }
        }
    }
}

Refactored Components (V2)

Class TrainingCompanyEditComponent incorrectly extends base class BaseAdminEditComponent, types have seperate declarations of private property 'alert'

@Component({
    selector: 'wk-training-company-edit',
    template: require('./edit.html')
})
export class TrainingCompanyEditComponent extends BaseAdminEditComponent implements OnInit, OnDestroy {

    // Class TrainingCompanyEditComponent incorrectly extends base class BaseAdminEditComponent, types have seperate declarations of private property 'alert'
    constructor(
                private alert: AlertService,
                private trainingCompanyService: TrainingCompanyService
                ) {

        // alert instead of this.alert
        super(alert);
    }
}

export class BaseAdminEditComponent {

    constructor(private alert: AlertService) {
    }

    protected handleSaveError(error: any) {

        if (error.message) {
            if (error.errors && _.isArray(error.errors) && error.errors.length > 0) {
                this.alert.error(_.join(error.errors, '\n'), error.message);
            }
            else {
                this.alert.error(error.message);
            }
        }
    }
}

Refactored Components (V3)

This works, just wondering if it is the best technique

@Component({
    selector: 'wk-training-company-edit',
    template: require('./edit.html')
})
export class TrainingCompanyEditComponent extends BaseAdminEditComponent implements OnInit, OnDestroy {

    // Class TrainingCompanyEditComponent incorrectly extends base class BaseAdminEditComponent, types have seperate declarations of private property 'alert'
    constructor(
                private alert: AlertService,
                private trainingCompanyService: TrainingCompanyService
                ) {

        // alert instead of this.alert
        super(alert);
    }
}

export class BaseAdminEditComponent {

    // Create a private variable with a different name, e.g. alert2
    private alert2: AlertService;

    constructor(alert: AlertService) {
        this.alert2 = alert;
    }

    protected handleSaveError(error: any) {

        if (error.message) {
            if (error.errors && _.isArray(error.errors) && error.errors.length > 0) {
                this.alert2.error(_.join(error.errors, '\n'), error.message);
            }
            else {
                this.alert2.error(error.message);
            }
        }
    }

}
1
  • Have you tried super(alert);? Commented Jan 10, 2017 at 3:54

4 Answers 4

14

Just set the access modifier of the constructor parameter in the derived class at the same level as that in the base class. i.e.

Base Class

import * as _ from "lodash";

import {AlertService} from '../common/alert/alert.service';

export class BaseAdminEditComponent {

    constructor(protected alert: AlertService) { }

    protected handleSaveError(error: any) {

        if (error.message) {
            if (error.errors && _.isArray(error.errors)) {
                console.error(error.errors);
            }
            this.alert.error(error.message);
        }
    }
}

Derived Class

@Component({
    selector: 'wk-training-company-edit',
    template: require('./edit.html')
})
export class TrainingCompanyEditComponent extends BaseAdminEditComponent {

    trainingCompany: TrainingCompany;

    trainingCompanyId: number;

    constructor(
        protected alert: AlertService,
        private validation: ValidationService,
        private trainingCompanyService: TrainingCompanyService) {

        super(alert);

        // Other Constructor Code Here
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

What does this add that's not covered in the accepted answer?
Well, it means you can use Typescript syntactic sugar - you no longer have to manually assign the constructor parameter
Perhaps some explanation what's the essence of the answer would help. I wasn't able to find it.
In the accepted answer, David says that it is important that you don't use constructor parameter syntactic sugar. That is not the case. If you ensure the access modifier is the same in both the base and derived class, you can still use syntactic sugar which means a reduction in verbosity, as you no longer have to assign the constructor parameter.
9

I finally worked out the pattern that works, it is important to not use the private (syntacting suger pattern) that Radim mentioned in the constructors.

I made the alert service a protected property on the base class.

And it is important to bind the base event handler to this handlerSaveError.bind(this)

Final working code is here.

Base Class

import * as _ from "lodash";

import {AlertService} from '../common/alert/alert.service';

export class BaseAdminEditComponent {

    protected alert: AlertService;

    constructor(alert: AlertService) {
        this.alert = alert;
    }

    protected handleSaveError(error: any) {

        if (error.message) {
            if (error.errors && _.isArray(error.errors)) {
                console.error(error.errors);
            }
            this.alert.error(error.message);
        }
    }
}

Component Instance Class

@Component({
    selector: 'wk-training-company-edit',
    template: require('./edit.html')
})
export class TrainingCompanyEditComponent extends BaseAdminEditComponent {

    trainingCompany: TrainingCompany;

    trainingCompanyId: number;

    constructor(alert: AlertService, // Don't use private property
                private validation: ValidationService,
                private trainingCompanyService: TrainingCompanyService) {

        super(alert);

        // Other Constructor Code Here
    }

    onSave($event) {

        console.log('Save TrainingCompany');

        this.trainingCompany = TrainingCompany.fromJson(this.form.value);

        console.log(JSON.stringify(this.trainingCompany, null, 2));

        var isNew = _.isNil(this.trainingCompany.id);

        this.trainingCompanyService
            .upsert$(this.trainingCompany)
            .subscribe((response: EntityResponse<TrainingCompany>) => {

                try {
                    this.alert.success('TrainingCompany updated');

                    this.modelChange.fire('training-company', isNew ? 'new' : 'update', this.trainingCompany);
                }
                catch (e) {
                    console.error(e);
                    throw e;
                }
            }, this.handleSaveError.bind(this)); // Common Error Handler from base class. NOTE: bind(this) is required

    }
}

Comments

2

The constructor definition with modifier keywords like this

export class TrainingCompanyEditComponent 
    extends BaseAdminEditComponent implements OnInit, OnDestroy {

    constructor(
        private alert: AlertService,
        private trainingCompanyService: TrainingCompanyService
    ) {
    }
    ...

is just a syntactic sugar for more verbose:

export class TrainingCompanyEditComponent 
    extends BaseAdminEditComponent implements OnInit, OnDestroy {

    private alert: AlertService,
    private trainingCompanyService: TrainingCompanyService

    constructor(
        alert: AlertService,
        trainingCompanyService: TrainingCompanyService
    ) {
        this.alert = alert; // assign incoming value to member
        this.trainingCompanyService = trainingCompanyService;
    }
    ...

So, this is on the background. And we can do that assignment only once super was called.. that's why we have to change a call using this.alert (access to member) into alert (passing the incoming value)

    constructor(
        private alert: AlertService,
        private trainingCompanyService: TrainingCompanyService
    ) {
        //super(this.alert);
        super(alert); // this.alert was not assign yet

        // next ?
        // the this.alert will be assigned for us...
    }

Play with this adjusted definition here

2 Comments

I have updated my question above, to take into account the alert vs this.alert issue and there are still errors. Essentially this example that you have here gives the following error "types have separate declarations of private property 'alert'"
I created a playground example for you. It should show how these adjustments would make it working... also, I defined base first...
0

try to move constructor into BaseAdminEditComponent ,and not override constructor in TrainingCompanyEditComponent.

@Component({
    selector: 'wk-training-company-edit',
    template: require('./edit.html')
})
export class TrainingCompanyEditComponent extends BaseAdminEditComponent implements OnInit, OnDestroy {

    
}

export class BaseAdminEditComponent {

    constructor(private alert: AlertService,
                private trainingCompanyService: TrainingCompanyService) {
    }

    protected handleSaveError(error: any) {

        if (error.message) {
            if (error.errors && _.isArray(error.errors) && error.errors.length > 0) {
                this.alert.error(_.join(error.errors, '\n'), error.message);
            }
            else {
                this.alert.error(error.message);
            }
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.