-
Notifications
You must be signed in to change notification settings - Fork 25.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Why styleguide suggest use class instead of interface? #19632
Comments
I can't comment on the reasoning of the styleguide authors, but I can provide some thoughts on why I find classes in many situations more suitable than interfaces and why I think the styleguide-advice is justified. The write-up became much longer than I intended, but hope you appreciate the reading. First you should note, that the styleguide-example gives an example which is related to dependency injection, so I'll refer to DI at first.
Based on that experience, I consider TypeScript interfaces and object-literals a good fit for parameter objects and actual value types. They are less beneficial for model types or app-global application state, IMHO. I am even used to pass JSON-deserialized plain objects into a proper class constructor before using them to get a proper prototype chain and well-defined instances (it seems like instantiating an instance twice but see here that it is still faster than setting the prototype manually on an already existing instance) . Yet I wouldn't dare to say its the way to go for everyone and sometimes exceptions to the rule still make sense. Nevertheless, I don't share general criticism of classes vs. object-literals in terms of "performance". Unless you need to create a real mass of tens of thousands of objects at once, speed is seldom a real matter, FWIW. At least in client-side JavaScript, even if you have grids with thousands of potential items you still almost always want to implement some kind of lazy loading for the reason of load-times, network latency and scalability, limited display size etc. not due to JS speed. So you often load and instantiate a batch of a few hundred items at once where performance issues aren't significant. If your constructor initializes all class properties at instantiation time, then some JS engines can apply hidden class optimization to optimize speed of object creation. In terms of memory-efficiency, classes are superior as soon as your model needs methods. So given all that, I think its fair to propose classes as a best-practice in a styleguide. * You could mitigate this with a factory pattern + interfaces but
|
Sometimes we only need the definition for the server data, some models. The interface can perfectly serve this purpose without introducing additional overhead for the final output, because interfaces are completely removed during compilation. |
Choosing what best fits requirements is always the best thing to do. The term consider in the styleguide implies exactly that. |
For me I have 2 situations where using interfaces + objects works much better than using classes, because of this we have no reason to use classes (for object models) in our apps. Universal State Transfer - Classes can be serialised into objects but they cannot be automatically instantiated from json back into the class, therefore it requires more work from the dev aswell as more code, when using object + interface requires 0 extra work and 0 runtime overhead. Firebase - For very similar reasons as above you cannot use classes with firebase, infact if you try use a class firebase will throw. |
My questions were the following: Interesting that the official Style Guide recommends using Classes over Interfaces (https://angular.io/guide/styleguide#interfaces). It doesn’t really say when, so it appears as if ‘always’ is the recommendation. Also interesting that the Classes section is so bare (https://angular.io/guide/styleguide#classes). I’m seeing some of this approach lately and I find it harder to read: export class MyGraph {
constructor(public name: string, public visits: number, public change: number, public denials: number, public date: Date,
public allowed: boolean, public weighted: boolean) {}
} Compared to: export interface MyGraph {
public name: string;
public visits: number;
public change: number;
public denials: number;
public allowed: boolean;
public weighted: boolean;
} Am I off base somehow in recommending to use OutcomeJust discussed this with @wardbell. He said that I should mention @kapunahelewong and suggest that this recommendation be clarified. His quote was "Maybe we weren’t clear. Classes for components, directives, pipes, and services. Because Angular and DI insist. Otherwise, the guide does not care." The current recommendations seem to indicate that using |
This can be removed or it can stay. The guide clearly calls this out as a consider. Which means
So if you disagree, then do not follow that. It's OK. The key is to consider your choice and be consistent. |
Yeah totally understand. The thing is when we review the pull request, we normally add style guide as the reference to suggest them follow best practice. But the thing I found is even I give the reason why better to use interface in this case, but they might say the style guide use class in this case instead of interface. Even they don't know why but they think there must be some reasons style guide do this. So they will insist to use class instead of interface. That's why I ask this question whether there's some reason to use class instead of interface in this case. |
Have them read the first paragraph (what I quoted above). "Consider" or more clearly "think about it and have a discussion with your team". We'll never get that type of wording perfect. But ... PRs are always welcome :) |
Hey @jenniferfell, this issue is part way addressed in that the style guide is updated. We still need to assess the example as in #21186 and see if it needs to be refactored. You could probably close this issue but it will serve as a good reference for one of the writers. |
Closing because the style guide is correct. Keeping #21186 open to consider the implementation of the TOH tutorial. Thanks, everyone. |
@about-code of course you can now inject interfaces with an InjectionToken:
|
@atodd-geoplan I would probably still prefer purely abstract classes over interfaces with DI - at least and in particular with NgModule providers or Component providers. I would also most likely choose an abstract class with tree-shakable providers, however I do see that your approach does provide a nice solution to something which bugs me at times, with tree-shakable providers, that is, the need to make an interface type depend on a particular default implementation (see below). 1. But let's assume we are not using tree-shakable providers, which was the premise in my comment above. Why would I continue to choose purely abstract classes over interfaces:When transpiling a purely abstract class to JavaScript, where the abstract class has only abstract methods and only uninitialized properties, what remains after transpilation is an exported variable pointing to an empty constructor function. This empty function is actually equal to the token constant you are exporting in your example. The advantage with the purely abstract class, though:
Example with NgModule decorator: @Injectable()
export abstract class IAuthenticationProvider {
// your interface as purely abstract class (think of it as an interface class);
// when transpiled to ES5 (with ES2015 modules), it should end up with something like
// `export const IAuthenticationProvider = function() {};`
// compare this to
// `export const AuthenticationProvider = new InjectionToken() {};`
// in your examples above;
}
// Implementation implements the abstract class
export class CognitoAuthenticationProvider implements IAuthenticationProvider {}
// We don't need @Inject() because Angular can infer the token from the type metadata
@Injectable()
export class AuthenticationService {
constructor(
private authenticationProvider: IAuthenticationProvider,
private http: HttpClient
) {}
}
// mapping the interface class to an implementation, with an NgModule provider in this case
@NgModule({
providers: [
{ provide: IAuthenticationProvider, useFactory: () => new CognitoAuthenticationProvider() }
]
})
export class AppModule {} 2. Tree-shakable providersYour example is still interesting with respect to tree-shakable providers. Here we often have the situation that an interface type has to be a default implementation or has to create a dependeny on such a concrete implementation via the @Injectable({
providedIn: 'root',
useFactory: () => new CognitoAuthenticationProvider()
})
export abstract class IAuthenticationProvider {} Requiring an interface type to know and depend on a particular implementation looks a bit strange if you know how DI and interfaces work in other languages, like Java. It's not a severe problem, because you can still choose to override a default implementation with an NgModules provider. Nevertheless it would be desirable if one could use @Injectable({ providedIn: 'root' })
export abstract class IAuthenticationProvider {}
@Injectable({ provideAs: IAuthenticationProvider })
export abstract class CognitoAuthenticationProvider implements IAuthenticationProvider {} Unfortunately this isn't possible, though. Therefore I find your example interesting in that it shows how to decouple an interface from a particular implementation when using tree-shakable providers as of today (see here for another example of what I mean). |
@about-code thanks for taking the time to show another way to do this. Both approaches are very similar. I have an inbuilt tendency to try and use the language constructs as they were meant to be - i never really think about compilation, or tree-shaking, i just want my code representative. I also come from a .Net background, where I heavily use interfaces for DI. So it is a habit thing more than anything. So I would probably stick with an interface and the |
Same goes for me. Thank you for your ideas. |
@atodd-geoplan Just let me add: it should be possible for you to omit the |
Another interesting thought is web assembly. Sure, it might seem like a good idea to not use Interface-based design, because we are compiling to javascript, and javascript doesn't have interfaces. I foresee that TypeScript will start compiling directly to web assembly and bypass javascript altogether. At that point, does it make sense to make a critical architectural decision based on a short term compilation target? Yagni probably says so, but an Api developer might want to consider interface-based design as a long term solution. Of course, when has anyone in the web/js world thought long term ;-). |
@rhyous So far any requests related to TS->WASM compilation were marked out of scope by the TypeScript team. There are only independent third-party experiments attempting to compile TS to WASM but they mostly use only a subset of TypeScript. WebAssembly isn't meant to replace JavaScript. So JavaScript is certainly not a "short-term compilation target" for TypeScript. Making architectural decisions based on such a vague assumption is critical, either. Using classes, abstract or non-abstract, is not really going against the principles of interface-based design. In fact you can use any class right-hand side of the |
Angular styleguide now recommends Interfaces for data models. It seems it has been updated later. |
I try to avoid interfaces whenever possible in favor of classes. The main reason is I can give default values and it prevents a lot of scared / defensive coding especially when I can set an empty array for a list. You also never know when you may need to add a get or method down the road. Furthermore, when you test things it makes setup go much quicker and most of the time people create an object manually instead of just using a constructor in the code anyhow. It just feels like it provides more value to me. Example below:
|
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
In style guid, it says consider use class instead of interface
https://angular.io/guide/styleguide#interfaces
Also in the example file hero.model.ts it's using class:
export class Hero {
id: number;
name: string;
}
I think in this case better use interface instead of class? As typescript best practice is that in this case. Unlike classes, interfaces are completely removed during compilation and so they will not add any unnecessary bloat to our final JavaScript code.
Any reason to use class instead of interface in this case?? Or it's better to use interface in this case?
The text was updated successfully, but these errors were encountered: