简体   繁体   中英

When to use Interface and Model in TypeScript / Angular

I recently watched a Tutorial on Angular 2 with TypeScript, but unsure when to use an Interface and when to use a Model for data structures.

Example of interface:

export interface IProduct {
    ProductNumber: number;
    ProductName: string;
    ProductDescription: string;
}

Example of Model:

export class Product {
    constructor(
        public ProductNumber: number,
        public ProductName: string,
        public ProductDescription: string
    ){}
}

I want to load a JSON data from a URL and bind to the Interface/Model. Sometime I want a single data object, other time I want to hold an array of the object.

Which one should I use and why?

Interfaces are only at compile time. This allows only you to check that the expected data received follows a particular structure. For this you can cast your content to this interface:

this.http.get('...')
    .map(res => <Product[]>res.json());

See these questions:

You can do something similar with class but the main differences with class are that they are present at runtime (constructor function) and you can define methods in them with processing. But, in this case, you need to instantiate objects to be able to use them:

this.http.get('...')
    .map(res => {
      var data = res.json();
      return data.map(d => {
        return new Product(d.productNumber,
          d.productName, d.productDescription);
      });
    });

The Interface describes either a contract for a class or a new type . It is a pure Typescript element, so it doesn't affect Javascript.

A model, and namely a class , is an actual JS function which is being used to generate new objects.

I want to load JSON data from a URL and bind to the Interface/Model.

Go for a model, otherwise it will still be JSON in your Javascript.

I personally use interfaces for my models, There hoewver are 3 schools regarding this question, and choosing one is most often based on your requirements:

1- Interfaces:

interface is a virtual structure that only exists within the context of TypeScript. The TypeScript compiler uses interfaces solely for type-checking purposes. Once your code is transpiled to its target language, it will be stripped from its interfaces - JavaScript isn't typed.

interface User {
 id: number;
 username: string;
}
// inheritance
interface UserDetails extends User {
 birthdate: Date;
 biography?: string;  // use the '?' annotation to mark this property as optionnal
}

Mapping server response to an interface is straight forward if you are using HttpClient from HttpClientModule if you are using Angular 4.3.x and above.

getUsers() :Observable<User[]> {
 return this.http.get<User[]>(url); // no need for '.map((res: Response) => res.json())' 
}

when to use interfaces:

  • You only need the definition for the server data without introducing additional overhead for the final output.
  • You only need to transmit data without any behaviors or logic (constructor initialization, methods)
  • You do not instantiate/create objects from your interface very often
    • Using simple object-literal notation let instance: FooInterface = { ... }; , you risk having semi-instances all over the place.
    • That doesn't enforce the constraints given by a class ( constructor or initialization logic, validation, encapsulation of private fields...Etc)
  • You need to define contracts/configurations for your systems (global configurations)

2- Classes:

A class defines the blueprints of an object. They express the logic, methods, and properties these objects will inherit.

class User {
 id: number;
 username: string;
 constructor(id :number, username: string)  {
  this.id = id;
  this.username = username.replace(/^\s+|\s+$/g, ''); // trim whitespaces and new lines
 }
}
// inheritance
class UserDetails extends User {
 birthdate: Date;
 biography?: string;  
 constructor(id :number, username: string, birthdate:Date, biography? :string )  {
   super(id,username);
  this.birthdate = ...;
 }
}

when to use classes:

  • You instantiate your class and change the instances state over time.
  • Instances of your class will need methods to query or mutate its state
  • When you want to associate behaviors with data more closely;
  • You enforce constraints on the creation of your instaces.
  • If you only write a bunch of properties assignments in your class, you might consider using a type instead.

2- Types:

With the latest versions of typescript, interfaces and types becoming more similar. types do not express logic or state inside your application. It is best to use types when you want to describe some form of information. They can describe varying shapes of data, ranging from simple constructs like strings, arrays, and objects. Like interfaces, types are only virtual structures that don't transpile to any javascript, they just help the compiler making our life easier.

type FamilySituation = 'single' | 'married' | 'divorced' | 'widow' ;...
type User = {
 id: number;
 username: string;
}
// inheritance
type UserDetails = User & {
  birthDate: Date;
  familySituation: FamilySituation ;
}

when to use types:

  • pass it around as concise function parameters
  • describe a class constructor parameters
  • document small or medium objects coming in or out from an API.
  • they don't carry state nor behavior

正如@ThierryTemplier 所说,用于从服务器接收数据并在组件之间传输模型(以保持智能感知列表并产生设计时错误),使用接口很好,但我认为将数据发送到服务器(DTO)最好使用类从模型自动映射 DTO 的优点。

Use interface when you just want to check for data type in order to make sure the response always has the necessary keys. Classes/models for more than just checking for data types. Plus, good to know that interfaces doesn't exist in javascript which means typescript will always handle interfaces at compile time unlick classes which are native to javascript.

In order to make your code flexible, we need to use interfaces. Create interfaces and pass the interface type in the constructor of class. This uses dependency injection.

Benefits:

  1. If there is change in the parameters of the interface, No need to change class. 2.For testing you can use mock data in constructor of class.

I would like to offer an example to emphasize a big reason why we need class over interface .

Let's say that our frontend has to render an Employer which it retrieves by calling a backend system.

The following interface would be enough to validate the response retrieved by the backend server so that we are sure that each property (name, lastName, age) comply with the defined type when delivered to frontend.

export interface Employer {
    name: string;
    lastName: string;
    age: number;
}

Now let's add some more complexity to the example.

  • Let's say that we want to also be able to print the fulname of an employer which will consist of

    {name}_{lastName}

  • We would like to also have a message printed if the Employer is on retirement age if he is more than 62 years old.

All this information is relevant with the employer and such information could be encapsulated inside the Employer code.

This could be easily achieved if we use a class model for our Employer.

 export class Employer {

        private static retirementAge = 62;
    
        constructor(
            public name: string,
            lastName: string,
            age: number
        ) {
        }
    
        public getFulName(): string {
            return `${this.name}_${this.lastName}`;
        }

        public getRetirementMessage(): string {
            if (this.age > Employer.retirementAge){
               return `{this.getFullName()} can apply for retirement`;
            } else {
               return `{this.getFullName()} can not apply for retirement`;
            }
        }
    }

An important note here!

The httpClient of Angular will create from the response of the backend an object representation for Employer in both cases (interface, class) and will also validate if fields from response object comply with defined type of fields on either class or interface. But in the case we use a class it will not create an instance from that class to represent the response!!

export class EmployerService{  

    public getEmployer(id: number) :Observable<Employer>{
        return this.http.get<Employer>(environment.apiUrl + '/employer/' + id);
      }

} 

 export class EmployerComponent {
  
    private employer: Employer;
    ....

    getEmployer(id: number) :void {
        this.employerService.getEmployer(id).subscribe((resp: Employer)=> {

        this.employer = resp; <--- This is not an instance of class Employer!!!
        this.employer.getFullName() <---- error method getFullName() does not 
                                          exist as this is not an instance of class Employer!
       });
     }

  }

So if we use class for our models we are responsible to instantiate our objects since httpClient will not do it for us. So for the following to work we would have to do the following

 export class EmployerComponent {
      
        private employer: Employer;
        ....
    
        getEmployer(id: number) :void {
            this.employerService.getEmployer(id).subscribe((resp: Employer)=> {

            this.employer = newEmployer(resp.name,  respp.lastName, resp.age);  
            this.employer.getFullName()  <--this will now work!
            });
         }
    
      }

Use Class instead of Interface that is what I discovered after all my research.

Why? A class alone is less code than a class-plus-interface. (anyway you may require a Class for data model)

Why? A class can act as an interface (use implements instead of extends).

Why? An interface-class can be a provider lookup token in Angular dependency injection.

from Angular Style Guide

Basically a Class can do all, what an Interface will do. So may never need to use an Interface .

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