TypeScript - Reflection and Decorator Metadata

John Lindquist
InstructorJohn Lindquist

Share this video with your friends

Send Tweet
Published 6 years ago
Updated 3 years ago

TypeScript allows you to emit decorator metadata which enables more powerful features through reflection. This lesson show you how decorators and reflection fit together and how to configure your own decorators to use reflection.

[00:00] When you look at the output of a typescript file, it's the class with the most basic decorators. So example, these would be the options. This is target class. It just returns its own target class. If you look at the output for this, you'll see that you have the class, and then the class is going to be assigned to that decorate.

[00:18] Then the next parameter is the person. Just like a decorator, it invokes the function and then passes in the things it wants to add, and then the original object. If we compare this to what happens if we switch on emit decorator metadata, we'll turn this to true. Then, I'll just make a minor change, add some white space to trigger the compiler. Switch over to main JS.

[00:43] You can see that it doesn't only have this decorate helper function, it also has this metadata helper function. Before it looked like this, and after it looks like this. If you want to, you can dive into these helper functions. They're defined up here. They decorate the metadata. They're not super complex, but they're very compact.

[01:00] I just want to show you how you can use them, though. Now that we have access to the metadata, what this does is it enables a reflection API, so that from this class or from this thing that's been decorated, I can say reflect get metadata. I want to get the param types. So you can see here, it's adding param types. Design param types.

[01:26] What param types means the types that come into the constructor. I want to get the param types off of the target class. In here, if I add a constructor and it has a param of name that's a string, when I hit save, you'll see some errors because we haven't installed Reflect yet, but we'll see the proper output anyway.

[01:55] You'll see that it adds the metadata of design param types, and then an array that has string, meaning that the first value in here, the name, the param, is a string. So, in the decorator, you can now know that the first value here is a string, or you know its type, so you can do additional things to this class before returning it.

[02:18] Without diving too deeply into dependency injection in Angular 2 or other frameworks, you can see how looking up types off of a constructor, being able to find them by looking them up through reflection and then returning a different version of it, that would enable you to use dependency injection.

[02:38] Now, let's see this in action. I'll go ahead and MPM install Reflect-metadata. Reflect metadata also has a dependency of Crypto. So I'll go ahead and save both of those in my index so they get loaded properly. I'll go ahead and duplicate these lines. Reflect-metadata is reflect-metadata in Node modules. Crypto points to Crypto.

[03:10] Because these aren't going to load more than one file, you can just load the main file. The main file I want from here is the Sha1 file. The main file I want from here is the Reflect.js. You can see these in Node modules.

[03:23] You can look in Node modules, Reflect, and find the Reflect.js file. Crypto has a Sha1 file. We just want to load those files whenever we say to import them. I'll go ahead and say import Reflect-metadata.

[03:42] Now the only error here is that this isn't passing it a string, so let's go ahead and do that to make the errors go away. Then if we refresh, we won't get any errors. Before, if we would have refreshed, you'll see the error is that get metadata doesn't exist. But now, when import Reflect metadata and this looks up Reflect and loads it, now when we refresh and save, it'll have the get metadata method on there.

[04:08] So, I can go ahead and get these types. I'll say types. Let's log out the types. Hit save. Hit refresh. You can see that the type, it's an array, and that's going to be a type of string. If I added another one, we'll say age, number. Hit save. We'll pass in a number here just to make the error go away, as well. Refresh. You can see now we have a string and a number.

[04:39] Similarly, if we added another class, we'll just call this cat. This can have the same example decorator. We'll have a different constructor this time. This time we'll say lazy is a boolean and hair is an object. We'll just say any. I'll hit save. Now when we refresh, you can see we also get the cat, here. We get the boolean and an object. This one was the person.

[05:14] That means we were able to make this generic, so that any class that comes through into this example decorator, we can look up its types and then use those types to modify or pass into the constructor, and return the class decorated however we want.

[05:27] If we come into the config and we delete emit decorator metadata, I'll hit save. I'll come over and just make a change so it recompiles. I'll refresh. You can see we just get undefined, undefined.

[05:38] That's again because it's not adding that metadata to my decorator unless I turn emit decorator metadata to true, make a minor change, hit save. You can see in my main JS file, we now have the metadata passed in. For the person, it's string and number. For the cat, it's boolean and object.