4

The way I see it, defining objects with models seems to make them rigid and less change tolerant, making it easier to break an application in the future and adding lines of code for no gain. For instance I could have a get method that returns a video object from an API and program it as Any or as a defined model.

/model/video.ts

export interface Video {
// my code
 }

/pages/videos.ts

getAllVideos(): Promise<Video> {
// my code
}

vs.

/pages/videos.ts

getAllVideos(): Promise<Any> {
// my code
}

The way I see it. Less lines of code, less complexity, less files and less rigidity is a good thing. Why even have models defined?

3 Answers 3

11

...making it easier to break an application in the future...

This is exactly the opposite of what happens when you introduce interfaces to JS with Typescript.

Let's say you have a function that accepts objects with a certain shape, like so

function giveMeStuff(obj) {
  return obj.foo.toLowerCase();
}

In this case, we have no way to make sure that when we call giveMeStuff we are actually passing an object which has foo property, which also needs to be a string.

If a new developer (or yourself, a few weeks later) comes along and calls giveMeStuff(12), the code will break at runtime.


Instead, this is what happens when you have an interface.

function giveMeStuff(obj: IObjectWithFoo): string {
  return obj.foo.toLowerCase();
}

interface IObjectWithFoo {
  foo: string;
}

now, when you try to call giveMeStuff(12) the compiler will warn you that you can't do that, because the function is expecting a different kind of argument.

Sign up to request clarification or add additional context in comments.

4 Comments

I see now. So we're moving error handling from runtime to the compiler and potentially getting better error messages as well.
The main advantage is the ability to catch preventable errors at compile time.
I do think it's a significant investment of labor (and app complexity) in exchange for moving the errors to compile time. However I also see how it would be very useful on large enterprise apps with lots of developers and complex deployments. Less so for small apps, it seems.
Well, note that the difference between an error at compile time and one at runtime is huge. In the first case, you won't be able to deploy your application with code that could potentially break (which is a very desirable condition), in the second one, you can do whatever you want, but the app will probably crash as soon as something unexpected happens.
3

There are situations where is more important have a well defined interface. It depends on which requirements you are fulfilling, on the risk of the application and so on.

For example in a particular project it may be more importat to force to use that method with that object type, reducing errors probabilities.

Comments

0

One important strategy that has not been included in any of the answers is to use interfaces to mine API/service response data structure contracts/expectations, facilitating identification of data structure mismatches between front-end vs back-end expectations of what the structure should be.

If your app uses asynchronous service/API requests, you will not be able to use interfaces directly to catch errors during compile time b/c the compiler does not know what the responses for each async request are. If no errors are thrown, you may never detect data type discrepancies.

However, if have a well-defined unit test suite that uses test data of type <interface-name>, Jasmine and Jest will catch any mismatches between your test data's structure and what the structure should be according to the interface. IMO this is an incredibly useful way of data-mining your service-endpoint/API responses to help ensure the front-end and back-end expectations of the structure of the data is in sync. This is especially important on larger projects which have multiple developers, or even multiple teams,working on different tasks. In my experience it is very common that the structures of data models become out-of-sync in these cases; team turnover and lack of documentation are prime culprits here.

You can simply copy objects from REAL service/API responses and use those as test data in your unit tests.

So let's say in your component:

this.service.getData()
.pipe(...)
.subscribe((resp: dataInterface[]) => {
    this.allData = resp;
});

... and suppose you have a spec.ts that loads its component with mockResponse:

const mockResponse: dataInterface[] = [
    {
        a: "test1",
        b: "test2"
    }, ...
];

where 

export interface dataInterface {
    a: string;
    b: string;
}
.....

providers: [
    {
        provide: ServiceRequest,
        useValue: {
            getData: () => of(mockResponse)
        }
    }
], ...

then this unit test will pass:

expect(component.allData).toEqual(mockResponse);

Now suppose over time, with team turnover, a newer team member - seeing no issues with the edit or no corrections in a pull request - changes the interface to

export interface dataInterface {
    a: string;
    // 'b' is removed b/c developer sees nothing break in the UI with its removal
}

If the IDE doesn't flag this and get the developer's attention, the Jasmine compiler will.

Here is a screenshot from our team's app where an interface called caseDetail was expecting a property called groupName. I had copied the service response directly from a real request's response and copied into my unit test. When I tried to run the test suite, the Jasmine compiler flagged the test data as missing this property:

groupName property is flagged as missing from test data

It's possible that this discrepancy would otherwise never have been caught, since no functionality on our app actually broke b/c groupName was missing in the response. I was then able to go back to our backend devs and create a spike to explore this discrepancy.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.