Search code examples
typescriptmongoosecastingassertionextending-classes

Understand Typescript syntax around Type assertions/casting


I'm a newbie to TypeScript.

I've been looking to find elegant patterns to define Mongoose schemas using TypeScript. I've been studying an article by Nicholas Mordecai at:

https://know-thy-code.com/mongoose-schemas-models-typescript/

It's a very helpful (thanks Nicholas)! There are though a few coding conventions I just can't get to the bottom of. The first relates to the following chunk of code ...

import { Schema, model, Document, Model } from 'mongoose';

declare interface IContact extends Document{
    name: string;
    email: string;
    phone?: string;
    message?: string;
    course_enquiry?: string;
    creation_date: Date;
}

export interface ContactModel extends Model<IContact> {};

The specific bit that's confusing me is:

 "Model<IContact> {};"

Of course, I can't even look at the generated js code because interfaces don't get transpiled.

ContactModel extends IContact, but only after IContact has been re-asserted/cast (from a Mongoose Document) to a Mongoose Model. Have I assumed correctly here? What's stumping me is simply what the code convention ...

"Class<interface> {};"

actually does/achieves!

The second question relates to the following piece of code, which Nicholas has contained within the same ts file as the above:

export class Contact {

    private _model: Model<IContact>;

    constructor() {
        const schema =  new Schema({
            name: { type: String, required: true },
            email: { type: String, required: true },
            phone: { type: String },
            message: { type: String },
            course_enquiry: { type: String },
            creation_date: { type: Date, default: Date.now }
        });

        this._model = model<IContact>('User', schema);
    }

    public get model(): Model<IContact> {
        return this._model
    }
}

The bit I would appreciate help on in here is the assessor get method...

public get model(): Model<IContact> {
        return this._model
}

I understand the principle of get assessors but I'm not sure whether the code after "model():" is returning a function or an instance of this._model (or both)?

Also, in a separate ts controller file, Nicholas uses the following two pieces of code to use the above...

import { connect, connection, Connection } from 'mongoose';
import { Contact, ContactModel } from './../models/contactsModel';

declare interface IModels {
    Contact: ContactModel;

}
        this._models = {
            Contact: new Contact().model
            // this is where we initialise all models
        }

NB. this._models is defined as "private _models: IModels;"

I'd really love to understand specifically and in detail what this code is doing! My interpretation is that a new Contact object is being created, which then uses the get assessor method model(), which returns an IContact interface (cast/asserted as a Model)??

Any help greatly appreciated!!

Many thanks.


Solution

  • In Model<IContact>, you are seeing a Generic Class. See the Docs about them here.

    In languages like C# and Java (and tsc) , one of the main tools in the toolbox for creating reusable components is generics, that is, being able to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types.

    A generic class has a similar shape to a generic interface. Generic classes have a generic type parameter list in angle brackets (<>) following the name of the class.

    Just as with interface, putting the type parameter on the class itself lets us make sure all of the properties of the class are working with the same type.