Run into circular reference issues with Typescript recently. https://github.com/microsoft/TypeScript/issues/27519 suggests using () => TypeName
but how do I use the variable?
export default abstract class State {
protected readonly _logger: ILogger;
protected readonly _name: StatusEnum;
protected readonly _colour: string;
protected readonly _context: StateContext; // XXX: Error here
protected constructor(@inject(LoggerTypes.ILogger) logger: ILogger, name: StatusEnum, context: () => StateContext) {
this._logger = logger;
this._name = name;
this._context = context;
this._colour = StatusColors[this._name] || StatusColors[StatusEnum.NEW]
}
public abstract handle (): void;
public Name (): StatusEnum { return this._name }
}
Build error:
error TS2322: Type '() => StateContext' is not assignable to type 'StateContext'.
15 this._context = context;
~~~~~~~
Domain/States/State.ts:15:25
15 this._context = context;
~~~~~~~
Did you mean to call this expression?
Answer provided by Fisherman seems to work. However, it hits the following error in State
subclass:
export default class NewState extends State {
private readonly _quoted: State;
public constructor(@inject(LoggerTypes.ILogger) logger: ILogger, context: () => StateContext) {
super(logger, StatusEnum.NEW, context);
this._quoted = new QuotedState(logger, this._context); // XXX: Error here
}
public override async handle () {
// Do operation
this._logger.Log(LogLevels.debug, "NewState.handle()")
await this._context.ChangeState(this._quoted);
}
}
Errors:
Domain/States/NewState.ts:12:48 - error TS2345: Argument of type 'StateContext' is not assignable to parameter of type '() => StateContext'.
Type 'StateContext' provides no match for the signature '(): StateContext'.
12 this._quoted = new QuotedState(logger, this._context);
~~~~~~~~~~~~~
Domain/States/QuotedState.ts:14:52 - error TS2345: Argument of type 'StateContext' is not assignable to parameter of type '() => StateContext'.
Type 'StateContext' provides no match for the signature '(): StateContext'.
14 this._approved = new ApprovedState(logger, this._context);
~~~~~~~~~~~~~
Domain/States/QuotedState.ts:15:52 - error TS2345: Argument of type 'StateContext' is not assignable to parameter of type '() => StateContext'.
Type 'StateContext' provides no match for the signature '(): StateContext'.
15 this._rejected = new RejectedState(logger, this._context);
You need to call the context function to get the StateContext instance before assigning it to this._context. Here's how you can do it:
typescript
export default abstract class State {
protected readonly _logger: ILogger;
protected readonly _name: StatusEnum;
protected readonly _colour: string;
protected readonly _context: StateContext; // No error here
protected constructor(
@inject(LoggerTypes.ILogger) logger: ILogger,
name: StatusEnum,
context: () => StateContext // Context is a function returning StateContext
) {
this._logger = logger;
this._name = name;
this._context = context(); // Call the function to get the StateContext instance
this._colour = StatusColors[this._name] || StatusColors[StatusEnum.NEW];
}
public abstract handle (): void;
public Name (): StatusEnum { return this._name; }
UPDATE
In your State abstract class, _context should be defined as a function that returns StateContext rather than StateContext directly. Modify the State class to store the context function instead of calling it immediately.
export default abstract class State {
protected readonly _logger: ILogger;
protected readonly _name: StatusEnum;
protected readonly _colour: string;
protected readonly _context: () => StateContext; // Store the function instead of StateContext itself
protected constructor(@inject(LoggerTypes.ILogger) logger: ILogger, name: StatusEnum, context: () => StateContext) {
this._logger = logger;
this._name = name;
this._context = context; // Store the function
this._colour = StatusColors[this._name] || StatusColors[StatusEnum.NEW];
}
public abstract handle(): void;
public Name(): StatusEnum { return this._name; }
}
When you need to access the StateContext instance in subclasses, you must call the _context function to get the actual instance. Update your NewState class accordingly:
export default class NewState extends State {
private readonly _quoted: State;
public constructor(@inject(LoggerTypes.ILogger) logger: ILogger, context: () => StateContext) {
super(logger, StatusEnum.NEW, context);
this._quoted = new QuotedState(logger, context); // Pass the context function, not this._context
}
public override async handle() {
// Do operation
this._logger.Log(LogLevels.debug, "NewState.handle()");
await this._context().ChangeState(this._quoted); // Call _context() to get the StateContext instance
}
}
Fix Other Subclasses:
Similarly, update any other subclasses to pass the context function and call it when accessing StateContext properties or methods:
typescript
export default class QuotedState extends State {
private readonly _approved: State;
private readonly _rejected: State;
public constructor(@inject(LoggerTypes.ILogger) logger: ILogger, context: () => StateContext) {
super(logger, StatusEnum.QUOTED, context);
this._approved = new ApprovedState(logger, context); // Pass the context function
this._rejected = new RejectedState(logger, context); // Pass the context function
}
public override async handle() {
this._logger.Log(LogLevels.debug, "QuotedState.handle()");
// Example of using the context
await this._context().ChangeState(this._approved); // Call _context() to get the StateContext instance
}
}
Calling the context Function:
Ensure that whenever you need to access properties or methods of StateContext, you do so by calling the this._context() function:
typescript
// Correct way to access the StateContext instance
this._context().ChangeState(this._quoted);