最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - TypeScript Decorators and Circular Dependencies - Stack Overflow

programmeradmin2浏览0评论

Consider the sample of inter-dependent code (below) that makes use of decorators.

Now consider the following workflow (yes, I do want to pass the actual exported classes since I need to use them later):

  1. App imports and runs Parent.ts
  2. @Test(Child) causes the app to import Child.ts while decorating
  3. Note: the class Parent has not yet been reached by the code
  4. In Child.ts, the @Test(Parent) decorator is executed
  5. At this point, Parent is undefined and cannot be passed to the decorator.

As you see, there's a nasty circular dependency going on and I cannot see a way of being able to apply decorators that take classes as arguments which reference each other.

Please note, I used @Test for brevity as an example. The actual decorators are @HasMany and @BelongsTo - so I do have an actual use case here.

My question to you: "Is there a solution to this issue?"

My fear is that there isn't, unless TypeScript's piled code is changed so as to defer the decoration process until all involved code has been imported.

Code example:

Decorators.ts:

    export function Test(passedClass: Function): Function {
        return function (model: Function): void {
            console.log(typeof passedClass);
        };
    }

Parent.ts:

    import {Child} from "./Child";
    import {Test} from "./Decorators";

    @Test(Child)
    export class Parent {

    }

Child.ts:

    import {Parent} from "./Parent";
    import {Test} from "./Decorators";

    @Test(Parent)
    export class Child {

    }

Consider the sample of inter-dependent code (below) that makes use of decorators.

Now consider the following workflow (yes, I do want to pass the actual exported classes since I need to use them later):

  1. App imports and runs Parent.ts
  2. @Test(Child) causes the app to import Child.ts while decorating
  3. Note: the class Parent has not yet been reached by the code
  4. In Child.ts, the @Test(Parent) decorator is executed
  5. At this point, Parent is undefined and cannot be passed to the decorator.

As you see, there's a nasty circular dependency going on and I cannot see a way of being able to apply decorators that take classes as arguments which reference each other.

Please note, I used @Test for brevity as an example. The actual decorators are @HasMany and @BelongsTo - so I do have an actual use case here.

My question to you: "Is there a solution to this issue?"

My fear is that there isn't, unless TypeScript's piled code is changed so as to defer the decoration process until all involved code has been imported.

Code example:

Decorators.ts:

    export function Test(passedClass: Function): Function {
        return function (model: Function): void {
            console.log(typeof passedClass);
        };
    }

Parent.ts:

    import {Child} from "./Child";
    import {Test} from "./Decorators";

    @Test(Child)
    export class Parent {

    }

Child.ts:

    import {Parent} from "./Parent";
    import {Test} from "./Decorators";

    @Test(Parent)
    export class Child {

    }
Share edited Sep 5, 2016 at 17:52 JDR asked Sep 5, 2016 at 16:40 JDRJDR 1,1441 gold badge13 silver badges33 bronze badges 2
  • It's not recursion as it's a circular dependencies. Why can't each class test itself? – Nitzan Tomer Commented Sep 5, 2016 at 17:05
  • Thanks - circular reference - that's the term I was grasping for. I named it @Test for brevity. It's actually @HasMany and @BelongsTo. – JDR Commented Sep 5, 2016 at 17:34
Add a ment  | 

3 Answers 3

Reset to default 6

Hit the same problem today. I solved it slightly differently, by replacing @Test(Parent) by @Test(() => Parent).

Instead of tracking the class constructor (Parent) in metadata, I track the thunk that returns the constructor (() => Parent). This delays the evaluation of the Parent imported variable until the thunk is invoked, which does the trick.

If you can postpone actions performed in decorator - you can overe the limitation and break circular dependency. From the names of your real decorators @HasMany and @BelongsTo it looks like you are attaching some sort of the metadata to each class for the later usage - if so here is my suggestion:

  1. Extend your @Test decorator signature

export function Test(passedClass: Function | string)

I assume here that the decorator will store meta information in some sort of static dictionary, like: . Where properties can look like

{
    hasMany: {new(): any} | string
    belongsTo: {new(): any} | string
}
  1. Inside decorator create new properties object with hasMany/belongsTo properties set to passedClass. If passedClass is not string - check all already added properties and replace any hasMany/belongsTo that are of string type and equal current passedClass.name

  2. Remove reference to Child from Parent.

This is somewhat naive implementation and you can implement some private fields instead to hide the intermediate string data and keep away from exposing union type fields.

Hope this will help you.

How about doing the same, but structuring your code differently?
If both Child and Parent reside in the same file then it shouldn't be a problem.

That might not sound optimal as it's fortable to separate code to modules due to length and logic, but that can be solved in a way.
You can have a main file that has base classes for those, even abstract:

// Base.ts
import {Test} from "./Decorators";

@Test(BaseChild)
export abstract class BaseParent {}

@Test(BaseParent)
export abstract class BaseChild {}

And then in your specific modules:

// Parent.ts
import {BaseParent} from "./Base";

export class Parent extends BaseParent {}

And

// Child.ts
import {BaseChild} from "./Base";

export class Child extends BaseChild {}
发布评论

评论列表(0)

  1. 暂无评论