2

I wanted to create a short documentation and explain the principle of Angular Signals using JavaScript proxies in an example. Then the question came up: could I quickly write it in TypeScript and set up the types? I said, of course, no problem, but then, surprise—somehow the types didn't fit anymore.

How can I use the correct types e.g. in:

target[property as keyof StateType] = value as StateType[keyof StateType];

outputs an error:

Type 'string | number' is not assignable to type 'never'.
  Type 'string' is not assignable to type 'never'.(2322)

This is the whole example (Online TS Playground):

type StateType = {
    count: number;
    name: string;
};

const state: StateType = {
    count: 0,
    name: 'React'
};

function onStateChange<K extends keyof StateType>(property: K, value: StateType[K]): void {
    console.log(`The property "${property}" was changed to "${value}"`);
}

const handler: ProxyHandler<StateType> = {
    set(target, property, value) {
        if (property in target) {
            target[property as keyof StateType] = value as StateType[keyof StateType];
            onStateChange(property as keyof StateType, value as StateType[keyof StateType]);
            return true;
        }
        return false;
    }
};

const proxyState = new Proxy(state, handler);

proxyState.count = 1; 
proxyState.name = 'Angular'; 
2
  • 1
    Hope this helps: Playground Commented Aug 19, 2024 at 16:36
  • Oh, that's also very elegant, thank you @HairyHandKerchief23 Commented Aug 19, 2024 at 16:46

1 Answer 1

2

Okay, I've got it. I would have needed to spend another 15 minutes on it. So that others don’t run into the same problem, here is the solution (1st Attempt):

type StateType = {
    count: number;
    name: string;
};

const state: StateType = {
    count: 0,
    name: 'React'
};

function onStateChange<K extends keyof StateType>(property: K, value: StateType[K]): void {
    console.log(`The property "${property}" was changed to "${value}"`);
}

const handler: ProxyHandler<StateType> = {
    set<K extends keyof StateType>(
        target: StateType,
        property: K,
        value: StateType[K]
    ): boolean {
        if (property in target) {
            target[property] = value;
            onStateChange(property, value);
            return true;
        }
        return false;
    }
};

const proxyState = new Proxy(state, handler);

proxyState.count = 1;
proxyState.name = 'Angular';

Short Explanation:

Generics in set Method ensures that prop and value both are correct typed due to StateType.

Edit & Update (2nd attempt, simplified, Online TS Playground):

type StateType = {
    count: number;
    name: string;
};

const state: StateType = {
    count: 0,
    name: 'React'
};

function onStateChange<K extends keyof StateType>(property: K, value: StateType[K]): void {
    console.log(`The property "${property}" was changed to "${value}"`);
}

const handler: ProxyHandler<StateType> = {
    set(
        target: StateType,
        property: string | symbol,
        value: any
    ): boolean {
        if (property in target) {
            const key = property as keyof StateType;
            (target[key] as any) = value;
            onStateChange(key, value as StateType[typeof key]);
            return true;
        }
        return false;
    }
};

const proxyState = new Proxy(state, handler);

proxyState.count = 1; 
proxyState.name = 'Angular';
Sign up to request clarification or add additional context in comments.

2 Comments

Something's wrong with this thought, this implies that property extends keyof StateType, which is not always the case, that's why he's checking property in target, a wider union does not extend a narrower one. Playground
Many thanks @HairyHandKerchief23, You're correct that property in a Proxy's set handler can be any string or symbol, not just keys of StateType. The in check is indeed there to handle cases where the property might not be a key of the target object. I've added an update addresses this issue. Would you please re-check it again.

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.