Narrator: So we got started with generics
and hopefully it's clear what TypeScript does here,
we pass some extra information into the merge function,
so that we can work in a better way
with the result of the merge function.
That's typically what generics are there for.
They allow you to continue working with your data in
TypeScript optimal way.
Now, in this case, here, however,
we would have a problem if I pass in,
let's say just a number 30 here.
Of course, TypeScript now throws an error,
because it tried to access the age,
but accessing the name would work.
Nonetheless, if we save that, it compiles without errors,
and if I print the overall merged object,
we see what I get back is just
hobbies and name in my object,
which makes sense because 30 is a number,
and when we passed that
as a second argument to object assign,
which we in the end are doing here,
well, then this just fails silently.
JavaScript doesn't throw an error, it doesn't complain.
But of course, it also doesn't merge 30
into the newly created object,
you see there is no 30 in this object.
Now, how would JavaScript do so?
Because 30 is not an object,
an object assign only is capable of merging different
objects with each other.
So what we in the end wanna save?
Well, we don't care about the exact type,
we don't care about the exact structure of the object
you're providing here for U and T.
But what we do care about is that both parameters,
both types T and U should be any kind of object
but they should be an object at all times.
And currently, we're not saying that.
Currently, we're just saying,
they should be of any type, I don't care.
Now often, that's not okay.
You want to restrict the types of T and U here,
solve your generic types.
You can do that with type constraints.
For generic types, you can set certain constraints regarding
the types your generic types can be based on
and you do this with the extended key word here in the
angled brackets after the type which you wanna constrain.
So here we could say extends object,
and with that I'm saying,
the T type can be any object with any structure,
but it has to be an object.
We can do the same for U.
And if I do that,
you see now I already get an error here in the IDE.
And if I try to save that,
I also get an error in the compiler,
because the type 30 argument here
is not assignable to type object.
So that's, of course, the behavior we want here.
Now we're forced to pass in an object again,
whoops, like this.
Now we're forced to pass this in again,
and therefore we improve this function
because now it really works.
And we know object assign wants two objects.
So in the end, we wanna guarantee
that we get two objects here
by setting certain constraints on our generic types.
And these constraints here can be anything.
You can refer to objects, to string,
you could use your own type if you had it.
You can also use union types here if you want to,
you're really flexible there,
you can set any constraints you want.
And of course, you don't have to constraint all
generic types.
If you just want to constrain U, you can do that.
In this example, it just makes sense to constraint both
because object assign wants two objects here,
but you're really flexible there.
It is important to be aware of that
concept of constraints though,
because that allows you to work with generic types
in a better in an optimal way,
which avoids unnecessary errors
or strange behaviors as we had it before.