user553086 user553086 - 18 days ago 7
TypeScript Question

How can a parent class determine which type was passed to a method, based on the subclass?

I'm trying to create subclasses to handle permissions for different areas of an app. Each subclass has a different list of valid actions that they handle, along with various reasons we show if the action is not allowed. I'm trying to define a

hasPermissions
method on the parent class to check if a permission is enabled, but I want it to complain if I pass in a string that is not a valid action for the current instance.

I know I can handle this by defining
hasPermissions
on each subclass, i.e.
hasPermissions (actionName: UserActions) { ... }
, but I'm hoping to avoid this. Is there a way the parent class can determine which actions are allowed, based on the the current instance?

declare var _;

type TPermission<TAction, TReason> = {
action: TAction;
enabled: boolean;
reason?: TReason;
}

class Permissions {
constructor(protected permissions) {
}

hasPermission(actionName) {
let permission = _.find(this.permissions, {
action: actionName
});

return permission ? permission.enabled : false;
}
}

type AdminActions = 'Add User' | 'Delete User';
type AdminReasons = 'MaxUsersReached' | 'CantDeleteAnotherAdmin';
type TAdminPermission = TPermission<AdminActions, AdminReasons>;

class AdminPermissions extends Permissions {
protected permissions: TAdminPermission[];

constructor(permissions: TAdminPermission[]) {
super(permissions);
}
}

type UserActions = 'Subscribe' | 'Unsubscribe';
type UserReasons = 'AlreadySubscribed' | 'AlreadyUnsubscribed';
type TUserPermission = TPermission<UserActions, UserReasons>;

class UserPermissions extends Permissions {
protected permissions: TUserPermission[];

constructor(permissions: TUserPermission[]) {
super(permissions);
}
}

let permissions: TUserPermission[] = [
{
action: 'Subscribe',
enabled: true
}
];

let user = new UserPermissions(permissions);

user.hasPermission('Subscribe'); // Valid, should return true
user.hasPermission('Unsubscribe'); // Valid, should return false
user.hasPermission('Add User'); // Invalid permission for UserPermissions, should error

Answer

You can make the parent class generic:

class Permissions<T extends string> {
    ...

    hasPermission(actionName: T) {
        ...
    }
}

And then:

class AdminPermissions extends Permissions<AdminActions> { ... }

class UserPermissions extends Permissions<UserActions> { ... }

Then the compiler will complain about this:

user.hasPermission('Add User');

Saying:

Argument of type '"Add User"' is not assignable to parameter of type 'UserActions'

As you wanted.

(code in playground)