Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

angular - Constructor Destructuring with exposed parameters

I have a constructor which uses destrucuring to simplify what needs to be passed to create the object with the proper defaults.

export class PageConfig {
  constructor({
    isSliding = false
  }: {
    isSliding?: boolean;
    getList: (pagingInfo: PagingInfo) => Observable<any>;
  }) {}
}

I want to expose the properties passed into the constructor as public, preferable without redeclaring them. Additionally I would not like to have a middle man object. e.g say I had a class which instanciated my object like so:

class UsersPage {

    config = new pageConfig({ this.getList })    

    getList(pagingInfo: PagingInfo) {
      // do work...
    }
}

I would like the config to expose 2 thing:

config.getList()
config.isSliding

How can I efficiently do this?

EDIT I've tried to do this by creating an base class which both the consturtor arguments and the class inherits from, However, if I omit the properties in the destructuring I won't be able to reference them in the constructor.

e.g

export class PageConfigArgs {
    isSliding?: boolean;
    getList: (pagingInfo: PagingInfo) => Observable<any>;
}

export class PageConfig extends PageConfigArgs {
  constructor({
    isSliding = false
  }: PageConfigArgs ) {
    super();
    this.isSliding = isSliding;
    //this fails since its not declared in the destructuring
    //However I do not want to declare it since it is required
    this.getList = getList;  
  }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

There's no simple way in TypeScript to programmatically copy properties from an object passed into a constructor to the object being constructed and have the compiler verify that it's safe. You can't do it with destructuring; that won't bring names into scope unless you mention them, and that even if you could do this you'd have to manually copy them into the constructed object anyway.

Similar in effect to destructuring is the function Object.assign(), so you could hope to have the constructor be like constructor(x: X){Object.assign(this, x)}... and this does work at runtime. But the compiler does not recognize that the properties have actually been set, and will tend to warn you:

class FailedPageConfig implements PageConfigArgs { // error!
  // Class 'FailedPageConfig' incorrectly implements interface 'PageConfigArgs'.
  //  Property 'getList' is missing in type 'FailedPageConfig'
  // but required in type 'PageConfigArgs'.
  constructor(config: PageConfigArgs) {
    Object.assign(this, config);
  }
}

You can manually fix that by using a definite assignment assertion for all "missing" properties, but this is a declaration you wanted to avoid, right?

class OkayPageConfig implements PageConfigArgs { 
  getList!: PageConfigArgs["getList"]; // definite assignment
  constructor(config: PageConfigArgs) {
    Object.assign(this, config);
  }
}

So, what else can we do?

One thing we can do is make a function that generates class constructors that use Object.assign(), and use a type assertion to tell the compiler not to worry about the fact that it can't verify the safety:

function ShallowCopyConstructor<T>() {
  return class {
    constructor(x: T) {
      Object.assign(this, x);
    }
  } as new (x: T) => T; // assertion here
}

And then you can use it like this:

export class PageConfigPossiblyUndefinedIsSliding extends ShallowCopyConstructor<
  PageConfigArgs
>() {}

declare const pcfgX: PageConfigPossiblyUndefinedIsSliding;
pcfgX.getList; // pagingInfo: PagingInfo) => Observable<any>
pcfgX.isSliding; // boolean | undefined

You can see that PageConfigPossiblyUndefinedIsSliding instances are known to have a getList and an isSliding property. Of course, isSliding is of type boolean | undefined, and you wanted a default false value, so that it would never be undefined, right? Here's how we'd do that:

export class PageConfig extends ShallowCopyConstructor<
  Required<PageConfigArgs>
>() {
  constructor(configArgs: PageConfigArgs) {
    super(Object.assign({ isSliding: false }, configArgs));
  }
}

declare const pcfg: PageConfig;
pcfg.getList; // pagingInfo: PagingInfo) => Observable<any>
pcfg.isSliding; // boolean

Here PageConfig extends ShallowCopyConstructor<Required<PageConfigArgs>>(), meaning that the superclass's constructor requires both getList and isSliding properties to be passed in (using the Required<T> utility type).

And the constructor of PageConfig only needs a PageConfigArgs, and assembles a Required<PageConfigArgs> from it using another Object.assign() call.

So now we have a PageConfig class whose constructor accepts PageConfigArgs and which constructs a Required<PageConfigArgs>.


Finally we get to your UsersPage class. You can't do new PageConfig({this.getList}). That's not valid syntax. Instead you can do this:

class UsersPage {
  config = new PageConfig(this);

  getList(pagingInfo: PagingInfo) {
    return null!;
  }
}

or this

class UsersPage {
  config = new PageConfig({getList: this.getList});

  getList(pagingInfo: PagingInfo) {
    return null!;
  }
}

or, if you don't want to type the word getList twice, and don't want to copy every property from this, then you can make a helper function called pick which copies named properties out of an object:

function pick<T, K extends keyof T>(obj: T, ...keys: K[]) {
  const ret = {} as Pick<T, K>;
  for (const k of keys) {
    ret[k] = obj[k];
  }
  return ret;
}

And then use this:

class UsersPage {
  config = new PageConfig(pick(this, "getList"));

  getList(pagingInfo: PagingInfo) {
    return null!;
  }
}

Okay there's a lot to unpack there. Hope it gives you some direction. Good luck!

Link to code


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...