61

I’m here today because I’ve a question about, like the title said, about classes and interfaces in Angular.

From my Point of View, I understand this:

Interfaces are used in Typescript to perform type-checking, they are here until the transpilation and disappear in the production. Also Interface cannot be used to instantiate.

Classes came from ES6 are also used for type-checking, but they stay after transpilation and generate code in the production. Also, they are used to instantiate.

So, basically, Interface is useful if we don’t need them in production, if we only need to type-check. On the opposite, Class are here if we need them in production, particularly for the instantiation.

I am Correct or Did I miss something about class and interface?

akramgassem
  • 111
  • 2
  • 9
Scieur Arnaud
  • 823
  • 2
  • 7
  • 13
  • 1
    Sounds about right. [more](https://toddmotto.com/classes-vs-interfaces-in-typescript) – Phix Jan 24 '19 at 23:14
  • ES6 Classes have nothing to do with type checking. Typescript Classes may or may not be used for type checking depending on if they are constructed using types. Typescript Interfaces have no other purpose than type checking – Randy Casburn Jan 24 '19 at 23:16
  • 2
    Possible duplicate of [Difference between interfaces and classes in Typescript](https://stackoverflow.com/questions/40973074/difference-between-interfaces-and-classes-in-typescript) – GreenTeaCake Jan 25 '19 at 00:27
  • This is a duplicate of https://stackoverflow.com/questions/51716808/when-use-a-interface-or-class-in-typescript and https://stackoverflow.com/questions/40973074/difference-between-interfaces-and-classes-in-typescript/40974330 – GreenTeaCake Jan 25 '19 at 00:29

2 Answers2

95

You're correct. Interfaces are great when you only need the type checking whereas classes are great when you not only want the type checking, but you need some methods or require some other logic.

Personally, I always start with just using an interface, and once I need some methods, I'll add a class and inherit the interface. I would also add that I prefer to always have an interface whether you're using a class or not. This allows you to pass around/inject the interface instead of having to re-instantiate class multiple times.

Here is an example of a typical pattern I would follow if I need some methods (which means I need a class)

interface IPerson {
    firstName: string;
    lastName: string;
    age: number;
    getFullName(): string;
}
class Person implements IPerson {
    public firstName: string;
    public lastName: string;
    public age: number;
    constructor(firstName: string, lastName: string, age: number) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    getFullName(): string {
        return `${this.firstName} ${this.lastName}`
    }
}

const person: IPerson = new Person('John', 'Doe', 44);

I would never inject or need to import Person anywhere unless I actually needed to 'new up' a new person. I can always inject IPerson instead and so long as what was passed in as the class, the interface will allow me call all the class methods and access the properties as I wish.

However, sometimes I don't need methods, but I need a data contract (or just want to suppress TypeScript from being pissed off I'm not telling it what everything is):

interface IPerson {
    firstName: string;
    lastName: string;
    age: number;
}

const person: IPerson = <IPerson>{
    firstName: 'John',
    lastName: 'Doe',
    age: 44
};

Doing it this way still gives you a way to create a variable on the fly that adheres to the interface. However, there are no methods when you do it this way (so you lose your getFullName function and would have to run that manually)

Overall, I find myself using interfaces on http returns that are structured json. I don't really need to do anything more to them on the front end as the backend has done the heavy lifting for me already. So, creating a class for each one of those returns may be overkill, but some people prefer it that way.

One other thing to bring up is that creating all these models is extremely time consuming. It pays off though. In one of my projects, I used an npm module to automatically generate TypeScript interfaces based off my c# models. That saved a ton of typing and thought it was pretty cool (https://www.npmjs.com/package/csharp-models-to-typescript)

One other popular solution is to use Swagger Codegen. You can 'swaggerize' your backend and have swagger codegen just generate your models and services for you. It's pretty slick. Here's an example repo using Angular and C# as the backend.

In general, it's really your preference. Just keep in mind, if you need methods and extra layers to build out a given model, a class is best. If you just need types and don't care about the rest of the fancy 'syntactical sugar' just use an interface. As you mentioned, interfaces disappear in production so it's certainly a plus.

https://jsfiddle.net/mswilson4040/3vznkbq0/7/

mwilson
  • 12,295
  • 7
  • 55
  • 95
  • Isn't it better to use interfaces for models instead of classes and keep all the logic inside services? If we add methods to a model we might need to inject services into the model at some point to get access to certain functionality (like retrieving translations, for example). This means that we either have to make the model injectable itself (which is something we should never do) or move methods that require injects into another service, therefore scattering the business logic among models/services. – KayO Oct 01 '19 at 11:35
  • 2
    You can technically do that. However, in the scenario described above, the 'extra logic' for the model doesn't ping a server or make any http requests. IMO services should be reserved for server communication / global functionality (such as even emitters or something). The actual data contract (model / interface) should be isolated from the service. The job of the service should strictly be http requests and being the 'data contract cop' in regards to making sure any data returned from the server / http request is getting hydrated into your models. Obviously, there are multiple ways to do this – mwilson Oct 01 '19 at 15:30
  • 1
    ... but, if you truly wanted to include your model in your service code and/or inject your service into your model code, you can do it. I would argue that at that point, you're trying to setup some light-weight ORM-like setup so you could do something like `await Person.getUserProfile()` which is fine, but not the way I'd set things up in a front-end world – mwilson Oct 01 '19 at 15:33
44

Since you also tagged Angular in your question, I wanted to add to the answer from an Angular perspective.

Angular also uses the Interface (or class) as a structure when retrieving data via http.

export interface Product {
  id: number;
  productName: string;
  productCode: string;
  tags?: string[];
  price: number;
  description: string;
  imageUrl: string;
}

@Injectable({ providedIn: 'root' })
export class ProductService {
  private productsUrl = 'api/products';

  constructor(private http: HttpClient) { }

  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(this.productsUrl);
  }
}

The http.get method gets the data and populates the provided product array structure. This makes it much easier to work with the data from the component.

DeborahK
  • 57,520
  • 12
  • 104
  • 129
  • 2
    I know I'm way late to the show but why not a class? especially if you would eventually provide methods to interact with your `Product`, or an array of `Products`, such as summing a field in the array etc. Or should most of that be done on the backend API? I've always struggled when to use Classes in an Angular application as most devs seem to think interfaces are the preferred construct. – ctilley79 Jun 27 '21 at 16:04
  • 5
    Now that Angular v12 has strict typing by default, a class would require that *every* field be initialized to something. An interface does not require that. Also, when the above code is executed, it does *not* create instances of the Product class for each retrieved item. Rather is *shapes* them into the class shape. So if you defined methods, you wouldn't be able to call them without first mapping the returned data objects into created objects from the Product class. For more info: https://stackoverflow.com/questions/51763745/angular-6-error-typeerror-is-not-a-function-but-it-is – DeborahK Jun 29 '21 at 22:00