Exploring Angular 2.3 Component Inheritance and the Importanc of Dependency Injection

Is there a way to share dependency injection between child and parent components using the new Angular 2.3 Component Inheritance?

For instance, I aim to transfer AlertService down into the parent component while keeping 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 approach works, but is it the most effective 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);
            }
        }
    }

}

Answer №1

To ensure proper access control in the derived class constructor, make sure to set the access modifier at the same level as in the base class. For example:

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);

        // Additional Constructor Code Here
    }
}

Answer №2

I have finally cracked the code and discovered a working pattern. It is crucial to avoid using the private (syntactic sugar pattern) that Radim mentioned in the constructors.

By making the alert service a protected property on the base class, I achieved success.

Additionally, it is essential to bind the base event handler to this handlerSaveError.bind(this)

Here is the finalized functional code.

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

    }
}

Answer №3

When defining a constructor with modifier keywords, it provides a more concise syntax:

export class TrainingCompanyEditComponent 
    extends BaseAdminEditComponent implements OnInit, OnDestroy {

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

This condensed syntax is essentially the same as the expanded version below:

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;
    }
    ...

The main difference lies in how we handle the assignment within the constructor function. The use of this.alert versus just alert indicates when the values are assigned.

    constructor(
        private alert: AlertService,
        private trainingCompanyService: TrainingCompanyService
    ) {
        super(alert); // We cannot access this.alert until after super is called
        
        // Further assignments to this.alert will follow...
    }

Feel free to explore and experiment with adjusting this code snippet here

Answer №4

Consider moving the constructor into the BaseAdminEditComponent and avoid overriding it in the 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);
            }
        }
    }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating a List programatically in material-ui can be easily achieved by following these steps

I am attempting to create a contact view using the list component from Material-UI. My code is written in typescript, but I am struggling with setting up react and material-ui correctly. Any guidance would be greatly appreciated. export interface IConta ...

When attempting to showcase an image within an Angular form, the error message "Form control with unspecified name attribute lacks a value accessor" is displayed

I have a scenario where I am overlaying icons on an image within my ngForm. The goal is to allow users to drag the icons and save their new location when the form is submitted. Everything works as expected, but I encounter a pesky error whenever the page l ...

What could be causing my sinon test to time out instead of throwing an error?

Here is the code snippet being tested: function timeout(): Promise<NodeJS.Timeout> { return new Promise(resolve => setTimeout(resolve, 0)); } async function router(publish: Publish): Promise<void> => { await timeout(); publish(&ap ...

Promise rejection: not as expected

I encountered an issue while using alert messages in my login menu: Runtime Error Uncaught (in promise): false Stack Error: Uncaught (in promise): false Here is the code snippet causing the problem: public login() { this.showLoading() this ...

The React component continuously refreshes whenever the screen is resized or a different tab is opened

I've encountered a bizarre issue on my portfolio site where a diagonal circle is generated every few seconds. The problem arises when I minimize the window or switch tabs, and upon returning, multiple circles populate the screen simultaneously. This b ...

Transferring data between a ComponentPortal within an Angular CDK

After attempting to implement the method described in a Stack Overflow thread on Angular CDK: How to set Inputs in a ComponentPortal, I've encountered issues with the deprecated PortalInjector without clear guidance on what to use instead. The depreca ...

Incorporate any enum value into a Typescript interface

I'm working with a Typescript interface export interface MyInterface { valid: boolean; resourceType: MyEnum; message: string; } As well as an enum enum MyEnum { 'a', 'b', 'c' } Is there a way to allow the ...

Which Angular2 npm packages should I be installing?

When I'm trying to create an empty app without using angular-cli, it's really difficult for me to figure out which packages or libraries to include. Searching for angular2 on npmjs yields unwanted results, forcing me to click through multiple li ...

Encountered a TypeScript error: Attempted to access property 'REPOSITORY' of an undefined variable

As I delve into TypeScript, a realm unfamiliar yet not entirely foreign due to my background in OO Design, confusion descends upon me like a veil. Within the confines of file application.ts, a code structure unfolds: class APPLICATION { constructor( ...

Issue 2339: Dealing with || and Union Types

I've encountered an interesting issue while working with TypeScript and JavaScript. I created a code snippet that runs perfectly in JavaScript but throws a syntax error in TypeScript. You can check it out in action at this TypeScript Sandbox. Essenti ...

Testing Angular Components with Jasmine and Karma: When handling the 'onChange' event, the changeEvent parameter of type MatRadioChange should not be void and must be assigned to a parameter of type

Hey there, I was working on a test for a call where I am using to emit the event: onChange(eventName: MatRadioChange): void { this.eventName.emit(eventName.value); } Here is the test I have written for it: describe('onChange', (eventName: ...

Exploring LocalStorage Monitoring in Vue.js 2

How can I stay informed about any changes in Banana.vue? I have tried using addEventListener, @watch but it doesn't seem to be working... The index.vue file is importing both Apple.vue and Banana.vue In Apple.vue: localStorage.setItem('fruit ...

Steps for Creating a User in the Meetup API

Exploring the functionalities of the meetup API has sparked my interest in incorporating it within my application to facilitate user creation, group joining, event hosting, and more. I am uncertain about the feasibility of this endeavor. Situation Imagi ...

How can a particular route parameter in Vue3 with Typescript be used to retrieve an array of strings?

Encountered a build error: src/views/IndividualProgramView.vue:18:63 - error TS2345: Argument of type 'string | string[]' is not assignable to parameter of type 'string'. Type 'string[]' is not assignable to type 'strin ...

Issue with displaying decimal places in Nivo HeatMap

While utilizing Nivo HeatMap, I have observed that the y value always requires a number. Even if I attempt to include decimal places (.00), it will still trim the trailing zeros and display the value without them. The expected format of the data is as foll ...

Invoking a functionality within a stream of events through an observable's subscribe

Service2.ts public flags$: BehaviorSubject<FlagName> = new BehaviorSubject<FlagName>("custom-flag-1"); This flag is set up as follows: private _setFlags = () => { const flagsData = this._customClient.getFlags(); if (f ...

Using React Native components from an external package leads to errors

I created a collection of React Native components by utilizing this template to seamlessly integrate Storybook. Additionally, I incorporated nativewind, which essentially serves as a React Native adaptation of Tailwind CSS. The structure of my components i ...

Experiencing problems with React createContext in Typescript?

I've encountered a strange issue with React Context and Typescript that I can't seem to figure out. Check out the working example here In the provided example, everything seems to be working as intended with managing state using the useContext ...

What is the best method for altering a route in React while utilizing Typescript?

I recently started coding along with the ZTM course and am working on a face recognition app. Instead of using JavaScript, I decided to use TypeScript for this project in order to avoid using the any type. However, as a beginner in this language, I'm ...

Angular 6: The Promise<any> is incompatible with the LeagueTable type

Recently delving into Angular and attempting to retrieve JSON League Table data from the API, I encountered an error message stating Type 'Promise' is not assignable to type 'LeagueTable'. leaguetable.service.ts import { Injectable } ...