If Typescript is the first language in which you've encountered generics, the concept can be quite difficult to understand. We skip the lecture in this lesson and dive straight into a real-world use-case that is guaranteed to help you understand the need for generics.
[00:00] Here we have a simple emitter class. It has a single emit method that takes an event and logs it to the console. Then we create a new instance, and we call it Emit Method. In this case, we pass it to an object literal that has the path and directory properties.
[00:16] Note, that at this point, this is all just plain JavaScript, so if inside our application we're expecting events to be in exactly this pattern, but you provide a typo, you're not going to get any warning about that here.
[00:29] To fix that, we can use an interface, and we'll call this "My Event." We'll say that path needs to be a string and directory is a Boolean. To use this interface, you could actually put it here. Now any typos will show up in the editor and also in the TypeScript compiler.
[00:51] That's not very useful, and having these right next to these object literals in the code can be a bit gross. You can move it to instead be on the function signature. Here you can say, "Emit takes a single argument."
[01:03] Here, we use the local name "event," and it must conform to the interface "My Event," which requires these properties. So far, so good. Now we have clean code, and any typos are picked up by the compiler, but we have a problem here.
[01:16] Let's say we want to reuse this class to create another emitter that is doing something different in our app. When it calls the emit method, we want to pass a type. We're going to pass it through a string which will be something like Start, and that's it.
[01:31] Now you can see we have this problem, because we hard coded the My Event interface into the class definition, and now this no longer conforms. How do we get around this? First of all, let's create the second interface.
[01:47] This one's a bit simpler. It just has type. We'll call this "My Event 2." How do we sometimes use one interface for one emitter, and sometimes use another interface for another emitter.
[01:59] Just for completeness, I'll show you that you could actually use the pipe here and say that it's "My Event," or "My Event 2," but you can imagine that this is not scalable either, and you're still coupling these interfaces into this class.
[02:13] We need something better, and this is exactly where generics can help us. You may have seen in some examples or in documentation the use of this syntax. "T" doesn't actually mean anything here. It could be a lowercase t or it could be the word car or anything you want, but T is the convention to use, T for type.
[02:30] All we can do is remove these from there and provide T. Now we have a generic class that has the type of T associated with it, and then we can reuse that type throughout any of the method signatures.
[02:41] Now onto the next problem. All of the sudden, we've lost all our type checking. Because we've said this is generic, at this point, it's allowing anything through. What we do is we provide the interface here when we instantiate the object.
[02:57] This first one will be My Event, and this one will be My Event 2. Note that we now get the red squiggly, as we have a type here. Correct that, and then call Emitter 2 instead, and you can see all ones have gone away.
[03:12] Now we have two separate interfaces, but we have a generic class, and this allows us to have type checking across multiple instances, all with different interfaces.
It would be nice to always use the noImplicitAny compiler flag to see when "any" are infered, and show us when there might be possible bugs.