Search code examples
angulartypescriptgenericstypesabstract-class

Extending the type of an object of an abstract class


The abstract class AbstractPanelComponent has a config object that stores some configuration information. When I extend the abstract class, I want to be able to extend the type of config to add custom configurations that are required for the implementation.

// Default Config Type
export type DefaultPanelConfig = {
    name: string
}

// Type I want to use for extending (to add additional info)
export type CustomConfig = {
    additionalConfig: string;
}

@Component({template: ''})
export abstract class AbstractPanelComponent {
    config: Partial<DefaultPanelConfig> = {};
}

@Component(...)
export class APanelComponent extends AbstractPanelComponent {
    // TODO: Expand the allowed Config somehow...
    override config: Partial<CustomConfig & DefaultPanelConfig> = {}
}

Using override config serves its purpose, but doesn't seem to be a good style. Is there a better way to implement such a config function, or is this already a good way? For example, I thought about using generics and something like <T extends Partial<DefaultPanelConfig>>, but I couldn't really figure out how I would use that. I look forward to your feedback!


Solution

  • You could use generics to make it more elegant. The trick is, that you can directly use Partial<T & DefaultPanelConfig> as your DefaultConfiguration. By doing so, you can easily extend it with a generic type and set a default generic type so that you don't have to extend it.

    In the following you can see a sample implementation:

    // Default Config Type
    type DefaultPanelConfig = {
        name: string
    }
    
    // Panel Configuration (Generic, so that it can easily be extended)
    // Intersect the default panel configuration with the generic to easily extend it
    type PanelConfiguration<T = {}> = Partial<T & DefaultPanelConfig>
    
    // Declare AbstractPanelComponent and set the generic to empty object by default.
    abstract class AbstractPanelComponent<T = {}> {
        config: PanelConfiguration<T> = {};
    }
    
    /* From here on: Two Test Cases */
    
    // Testing "Default" PanelConfiguration
    class APanelComponent extends AbstractPanelComponent {
        // No generic type: Use DefaultPanelConfig and nothing more
        override config: PanelConfiguration = {
            name: "Custom Configuraton", // Valid
            /* additionalConfig: "More Configuration", */ // Invalid (as supposed to)
            /* invalid: "invalid" */ // Invalid (as supposed to)
        }
    }
    
    // Declare Custom Config for testing
    type CustomConfig = {
        additionalConfig: string;
    }
    
    // Testing Extending "Default" Panel Configuration with Custom Configuration
    class BPanelComponent extends AbstractPanelComponent<CustomConfig> {
        // Expand the Config by simply adding the Generic Type
        override config: PanelConfiguration<CustomConfig> = {
            name: "Custom Configuraton", // Valid
            additionalConfig: "More Configuration", // Valid
            /* invalid: "invalid" */ // Invalid (as supposed to)
        }
    }