The other answers here are correct, but I want to add that you might consider using generic parameter defaults if you are usually (but not always) going to just pass in FooOptions<T> as the type of O:
interface FooOptions<T> {
doSomething: (e: T) => void;
}
// note how O has a default value now
export interface FooInterface<T, O extends FooOptions<T> = FooOptions<T>> {
items: T[];
options: O;
}
That lets you just leave out the O parameter when you intend it to just be FooOptions<T>:
const t: FooInterface<string> = {
items: ["a", "b", "c"],
options: {
doSomething: (e) => {
console.log(e);
}
}
}
And in the event that you actually want O to be more specific than FooOptions<T> (that's why you have it as a separate parameter, right?), you can still do it:
interface MoreOptions {
doSomething: (e: string) => void;
doNothing: () => void;
}
const u: FooInterface<string, MoreOptions> = {
items: ["d", "e", "f"],
options: {
doSomething(e) { console.log(e); },
doNothing() { console.log("lol nvm"); }
}
}
Oh, and if T will usually be string then you can add a default for it too and then FooInterface without parameters will be interpreted as FooInterface<string, FooOptions<string>>:
export interface FooInterface<T = string, O extends FooOptions<T> = FooOptions<T>> {
items: T[];
options: O;
}
const t: FooInterface = {
items: ["a", "b", "c"],
options: {
doSomething: (e) => {
console.log(e);
}
}
}
Okay, hope that helps. Good luck!