简体   繁体   中英

Given an interface and implementing class, how can I mass-assign properties in constructor?

I have an interface with an extensive list of properties, each with different types and structures:

interface OrderInterface {
  id: number;
  orderNumber: string;
  createdAt: string;
  updatedAt: string;
  customer: {
    id: number;
    firstName: string;
    lastName: string;
    email: string;
    address: {
      street: string;
      city: string;
      state: string;
      zip: string;
      country: string;
    }
  };
  products: {
    id: number;
    name: string;
    qty: number;
    isVirtual: boolean;
  }[];

  // ... assume we add an arbitrary number of fields.

}

Initially, the interface satisfied my needs for simply passing data around and ensuring the structure is the same in all parts used and to satisfy TypeScript's checking. Now, I find myself needing to perform some functions on the data which would be easy if methods were tied to the data directly. I start to write the class, and I realize that my first inclination is that I have to copy and assign every single property in the constructor.

class Order implements OrderInterface {

  id: number;
  orderNumber: string;
  createdAt: string;
  updatedAt: string;
  customer: {
    id: number;
    firstName: string;
    lastName: string;
    email: string;
    address: {
      street: string;
      city: string;
      state: string;
      zip: string;
      country: string;
    }
  };
  products: {
    id: number;
    name: string;
    qty: number;
    isVirtual: boolean;
  }[];

  // Assume the arbitrary fields defined in interface are defined here too

  constructor(data: OrderInterface) {
    this.id = data.id;
    this.orderNumber = data.orderNumber;
    this.createdAt = data.createdAt;
    // ...
    // And I have to duplicate every property? What if a property changes? Is there a better way?

  }

  get customerName(): string {
    return `${this.customer.firstName} ${this.customer.lastName}` || '(name not available';
  }

  get physicalProducts(): Product[] {
    // filter products by isVirtual property and return result.
  }

  // ... assume more functions to get, compare, change data, etc.

}

interface Product {
  id: number;
  name: string;
  qty: number;
  isVirtual: boolean;
}

In plain JS, I think this would work in the constructor. Essentially, it would just copy all the fields from my interface to the object.

constructor(data: OrderInterface) {
  Object.assign(this, data);
} 

But with TypeScript, I get an error that resembles this (changed from my real code)

Error: src/app/shared/inquiry.service.ts:378:3 - error TS2564: Property 'id' has no initializer and is not definitely assigned in the constructor.
Error: src/app/shared/inquiry.service.ts:379:3 - error TS2564: Property 'orderNumber' has no initializer and is not definitely assigned in the constructor.
Error: src/app/shared/inquiry.service.ts:380:3 - error TS2564: Property 'createdAt' has no initializer and is not definitely assigned in the constructor.
Error: src/app/shared/inquiry.service.ts:381:3 - error TS2564: Property 'updatedAt' has no initializer and is not definitely assigned in the constructor.
Error: src/app/shared/inquiry.service.ts:382:3 - error TS2564: Property 'customer' has no initializer and is not definitely assigned in the constructor.
Error: src/app/shared/inquiry.service.ts:395:3 - error TS2564: Property 'products' has no initializer and is not definitely assigned in the constructor.

// And continued Error for arbitrary property having no initializer nor being assigned in constructor

Even though technically all the correct fields should be converted to the new class instantiation, TypeScript doesn't seem to recognize this happening. What is an elegant way to do this in the constructor?

Or, is trying to convert this interface to a class "not the Angular way" to begin with? Is it more "Angular" to keep the data as an interface and put any functions dealing with model(s) in service classes?

Per @KSoze's recommendation in their comment, I decided to keep the "data" as an interface and the "business logic" as a service:

order.model.ts

export interface OrderInterface {
  id: number;
  orderNumber: string;
  createdAt: string;
  updatedAt: string;
  customer: {
    id: number;
    firstName: string;
    lastName: string;
    email: string;
  address: {
    street: string;
    city: string;
    state: string;
    zip: string;
    country: string;
  };
  products: {
    id: number;
    name: string;
    qty: number;
    isVirtual: boolean;
  }[];
}

order.service.ts

import {OrderInterface} from './order.model.ts';

export class OrderService {
  get customerName(order: OrderInterface): string {
    return `${order.customer.firstName} ${order.customer.lastName}` || '(name not available';
  }

  get physicalProducts(order: OrderInterface): Product[] {
    // filter products by isVirtual property and return result.
  }
}

If I were to do this again more-similarly to my original intent, I could use the "not-null assertion operator" on every required field (assuming I didn't have a default value I could set to the field instead). From my experience, the class fields would still be type-checked in their usage other than the constructor.

export class Order implements OrderInterface {
  id!: number;
  orderNumber!: string;
  createdAt!: string;
  updatedAt!: string;
  customer!: {
    id: number;
    firstName: string;
    lastName: string;
    email: string;
    address: {
      street: string;
      city: string;
      state: string;
      zip: string;
      country: string;
    }
  };
  products!: {
    id: number;
    name: string;
    qty: number;
    isVirtual: boolean;
  }[];

  constructor(data: OrderInterface) {
    Object.assign(this, data);
    // No errors. Typescript's "!" operator assumes the fields are defined upon instantiation.
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM