Web framework with typescript – refactor using Inheritance

source code

What we accomplished here uses composition.

Problems:

  • properties are public
  • Sync, Eventing and Attributes are hard types. We should use interfaces to components can be swapped in and out.
  • Make general functionalities reusable

Composition worse problem is nested properties.

Thus, let’s create a class Model that solves some of these problems. In order to do so, this class Model will have private properties that:

1) can hide the objects that deal with inner workings of our class.

2) use interfaces that will allow us to switch out components. Thus, it’s not hard coded. As long as our components adhere to those interfaces, we’re good to go.

3) We will also use generic type T so that the compiler will recognize the attributes and methods of any objects that we will pass in. That way, there will be type data available and intellisense can show us what we need.

Interfaces for the components

First we create interfaces for the components that will be used in Models.
This is so that as long as other components conform to these interfaces, we can interchange them.

For example, we need a component that represents Syncing. So we create interface Sync where you must implement:

1) fetch that takes in an id of type number and returns AxiosPromise.
2) save that takes in data of generic type T and returns AxiosPromise

We need a component that takes care of settings and getting local attributes, so we create interface ModelAttributes where:

1) get a certain type, where the type exists in keys of generic type T
2) getAll attributes where we return the whole object

Finally, we create an interface Events, where we force the component to implement:

1) on, with string param eventName, and a Callback so that the system can update you.
2) trigger, with string param eventName

But first, let’s look at what T means and how it is used.

Model class

We create Model class with generic type T.

We create private properties that has interface types T, which forces our data to have certain properties and to be constructed in a certain way. Notice that T acts as the generic type for some interfaces (ModelAttributes, Sync) in our properties.

Now, we create our Class that extends Model so all models can share general functionalities.

Let’s first declare a type that we can use for generic type T.

Declaring an interface for generic type T

ref – http://chineseruleof8.com/code/index.php/2021/12/06/generics/

We create an interface that act as a generic type.

Then we create a class User where it extends form Model.

This means User automatically takes on all attributes and methods of Model.
This is because those attributes and methods of Model are very general which we would to exist for any future classes that deals with Models. i.e User, Buildings, Animals….etc.

Now let’s look at the private attributes.

ModelAttributes is an interface that says you must implement these functions, for whatever generic type T these objects are.

We instantiate class Attributes, which conforms to ModelAttributes by having methods set, getAll, and get

Events is an interface that says you must implement these functions.

We instantiate class Eventing, which conforms to by having methods on, trigger

Sync is an interface that says you must implement these functions, for whatever generic type T these objects are.

We instantiate class ApiSync, which conforms to by having methods fetch, save, and get

Functions with generic type T

Look at the functions in our classes Attribute and ApiSync:

or:

Notice T. T simply means generic interface. In our case of User extends Model, we have declared T to be UserProps like so:

Look at

Attributes’ constructor, we pass in an object that conforms to UserProps (has optional properties id, name, and age):

gets passed into Attributes’ constructor:

where

Attributes’ getAll() returns UserProps, which we can then pass into
Sync’s save():


Just remember to declare a generic type T (i.e UserProps) when we extend User from Model

Constraints on Generic Type

Now, take a look at ModelMustHave:

The generic type T extends from interface ModelMustHave.
This means that the allowed optional attributes presented in UserProps are limited to ModelMustHave, which is:

-id
-name

This limit comes in handy when we’re trying to implement get, where we want to limit the kind of property strings to allow in the key parameter.

Let’s take a detailed look:

keyof T – http://chineseruleof8.com/code/index.php/2021/12/01/in-typescript/

If it was just T, our union would be “id” | “name” | “age”

In our case, T extends ModelMustHave for User

So hence, only “name” and “id” will work because ModelMustHave have constrained it to “name” | “id” only.

In other words, if the T are used in parameters, then we can only pass in “id” or “name”, as T extends ModelMustHave means we are constrained to “name” and “id”.

Notice the components that conform to interface with generic T must work together like this. It returns T and takes in parameter T.

then in class ApiSync, we must clarify what T extends to again in order to extract the constrained properties as shown in function save.

We import ModelMustHave for T, so that in save, we can extract those properties from T.

If we do not declare and extend ModelMustHave, the compiler will complain because our generic type T guarantees this type checking:

Creating a wrapper around the composition

Then by using these properties, we create a wrapper around their functionalities in order to do what we want to do.

For example, if we want to register and trigger events, we just use events.
But we if want to set data, we must use attributes to update locally, then use events to trigger

As you can see, we have created an interface layer for it.

In addition, we know that this.events.on means we want to register events, but we don’t want to always type out this.event.on, so we rather just say this.on

Then, we can return references like so:

this is so that in our code, we can do this: