Typescript factory class using logic - javascript

I have a typescript class inheriting another one. I would like to create a factory class that creates an object of one or the other using basic logic, but it is not working.
This is a basic class for a Customer:
class Customer {
static member = true;
id:string;
static c_type = "Basic Customer";
makeTransaction():string {
var transaction_id = Math.random().toString(36).substr(2, 9);
console.log(this.constructor.toString().split ('(' || /s+/)[0].split (' ' || /s+/)[1]);
return transaction_id;
}
constructor(public name:string, public dob:string) {
this.id = Math.random().toString(36).substr(2, 9);
}
}
This class extends customers to create a VIP customer:
class VIPCustomer extends Customer{
vip_num:string;
vip_discount:number;
static c_type = "VIP Customer";
constructor(public name:string, public dob:string) {
super(name, dob);
this.vip_num = Math.random().toString(36).substr(2, 9);
}
}
The customer creator is intended to create either a VIP customer or regular customer based on a string comparison, but it is not working.
class CustomerCreator {
static create(event: {name:string; dob: string}, type:string) {
console.log('Log type' + typeof type);
if (type === 'Basic') {
console.log('basic customer created');
return new Customer(event.name, event.dob);
}
if (type === 'VIP') {
console.log('basic customer created');
return new VIPCustomer(event.name, event.dob);
}
}
}
console.log(Customer.c_type);
console.log(VIPCustomer.c_type);
const customer_1 = CustomerCreator.create({name:'Pii', dob:'03/19'}, 'VIP');
var customer_2 = CustomerCreator.create({name:'Matthew', dob:'12/70'}, 'Basic');
//accessing an attribute
console.log(customer_1.name);
console.log(customer_1.id);
//console.log(customer_1.vip_num)
If you uncomment the last print statement, the code does not compile. The print statements also indicate that a basic customer is being created for both the customers 1 and 2, despite the string comparison. Where am I going wrong?

Typescript only has type info of compiling time, but not type info only known in run time.
The return type of CustomerCreator.create is Customer|VIPCustomer which is narrowed down to Customer so everything return from that function is recognized to ts compiler as Customer . That's the whole point of Factory pattern, that your code rely on interface but not class
If you really want to let compiler know what exact type of what CustomerCreator.create returns, you could try following code
type CreatorResult = {
Basic: Customer,
VIP: VIPCustomer
}
class CustomerCreator {
static create<T extends 'Basic'| 'VIP'>(event: {name:string; dob: string}, type:T): CreatorResult[T] {
although this is not recommended

Your solution is not working because the create factory method always returns the type Customer as VIPCustomer is also derived from Customer. Also, your create function not only returns just Customer but Customer | undefined because you do not have a default case (when type is neither Basic or VIP). I would just create multiple factory methods for each type of customer. In this case of my example there is almost no shared piece of code or extra processing, the factory pattern is rendered useless.
class CustomerCreator {
static create(event: { name: string; dob: string }) {
return new Customer(event.name, event.dob);
}
static createVip(event: { name: string; dob: string }) {
return new VIPCustomer(event.name, event.dob);
}
}
console.log(Customer.c_type);
console.log(VIPCustomer.c_type);
const customer_1 = CustomerCreator.createVip({ name: 'Pii', dob: '03/19' });
var customer_2 = CustomerCreator.create({ name: 'Matthew', dob: '12/70' });
console.log(customer_1.name);
console.log(customer_1.id);
console.log(customer_1.vip_num)

Related

Using Typescript interface as a variable

I am working on a Node Js (TypeScript) architecture and for some reason, I want to bind my interface to a specific object. I am making a general class that is extended by other subclasses and it will have a very general code. So my code looks like
interface User {
name: string;
}
interface Profile {
title: string;
}
class Parent {
name: string;
interface: Interface; // Help required here, getting error can't use type as a variable
constructor( name, interface ) {
// Load schema and store here
this.name = name
this.interface = interface
}
// Though this is not correct I hope you get the idea of what I am trying to do
get (): this.interface {
// fetch the data and return
return data
}
set (data: this.interface): void {
// adding new data
}
}
class UserSchema extends Parent {
// Class with custom functions for UserSchema
}
class ProfileSchema extends Parent {
// Class with custom functions for ProfileSchema
}
// Config file that saves the configs for different modules
const moduleConfig = [
{
name: "User Module",
class: UserSchema,
interface: User
},
{
name: "Profile Module",
class: ProfileSchema,
interface: Profile
},
]
const allModules = {}
// Loading the modules
moduleConfig.map(config => {
allModules[config.name] = new config.class(
config.name,
config.interface
)
})
export allModules;
I need suggestions on how should I bind my interfaces with their respective configs. Till now I have had no luck with that.
PS: All this code is separated into their respective files.
This is the use case for generics. You can even see them as "variable for types".
Instead of having an interface property in your Parent class, the latter would have a generic type:
class Parent<T> { // T is the generic type
name: string;
// interface: Interface; // generic is already provided at class level
constructor( name ) {
// Load schema and store here
this.name = name
}
get (): T {
// fetch the data and return
return data
}
set (data: T): void {
// adding new data
}
}
// Here you specify the concrete generic type
class UserSchema extends Parent<User> {
// Class with custom functions for UserSchema
}
class ProfileSchema extends Parent<Profile> {
// Class with custom functions for ProfileSchema
}

Typescript: Extend class with Partial<Type> constructor

Using typescript, how do extend the User class using Partial<User> as the constructor?
I am also open to solutions which do not use Partial. In this case I am only using the utility type to initialize a blank class. i.e. new User({})
Currently, AdvancedUser only has User properties, but none of the additional advanced?: properties.
export class User {
first_name: string = ''
last_name: string = ''
email: string = ''
constructor(data: Partial<User>) {
Object.assign(this, data)
}
}
export class AdvancedUser extends User {
advanced?: {
foo?: string
}
constructor(data: Partial<User>) {
super(data)
}
}
The provide code actually works. My project was suffering from a downstream typo reverting my AdvancedUser() call back to User().
I am also open to solutions which do not use Partial. In this case I am only using the utility type to initialize a blank class. i.e. new User({})
Instead of having constructors that use Partial, you can get the result you want by using the as keyword, which in my opinion is much cleaner.
As for the advanced property, the reason it's not showing up is because it isn't initialized anywhere (neither inline or in the constructor). Assuming you want to keep it as an optional property, all you need to do is initialize it with undefined:
export class User {
first_name: string = '';
last_name: string = '';
email: string = '';
constructor(data: User) {
Object.assign(this, data);
}
}
export class AdvancedUser extends User {
advanced?: {
foo?: string
} = undefined;
constructor(data: User) {
super(data);
}
}
const au = new AdvancedUser({} as User);
/* OUTPUT:
AdvancedUser: {
"first_name": "",
"last_name": "",
"email": "",
"advanced": undefined
}
*/
console.log(au);
How about something like this for using Partial?:
interface IUserData {
first_name: string;
last_name: string;
email: string;
}
interface IAdvancedUserData {
doAdvancedStuff(IAdvancedStuffOptions) : string;
}
class AdvancedUserData implements IUserData, IAdvancedUserData {
}
your User still accepts data of type Partial, then you pass an AdvancedUserData to your AdvancedUser constructor.

Which is recommended to use in Typescript? A class type object or any type with many params?

I am new to Typescript and Javascript. I have written the following piece of code which works fine. I want to understand which is good and recommended to use in Typescript. Let me explain a bit. When we pass more than 4 parameters lets say 8 parameters, sonar complains. So we create an object and populate all the required fields and pass to a function and we get the result. We can also define all the fields inside curly braces like this given below.
const pType: any = {firstName: "John", lastName: "Abraham"};
At the same time, we can define a class like this.
export class Person {
private _firstName: string = "";
private _lastName: string = "";
// All get set methods/functions
}
Please help me to understand the difference between the above two, which is better and why in terms of memoray usage. I have written the sample class for simplicity.
export class PassNReturnMultipleParams {
public getAddress_Type1(person: Person): Address {
console.log("First Name: ", person.firstName);
// write logic to validate
const address: Address = new Address();
address.cityName = "Bangalore";
address.flatName = "#123";
address.streetName = "Richmond Road";
return address;
}
public getAddress_Type2(pType: any): any {
console.log("First Name: ", pType.firstName);
// write logic to validate
const adrsType: any = {cityName: "Bangalore", flatName: "#123", streetName: "Richmond Road"};
return adrsType;
}
public check_Type1() {
const person: Person = new Person();
person.firstName = "John";
// Set other values
const adrs: Address = this.getAddress_Type1(person);
console.log("Address City Name: ", adrs.cityName);
}
public check_Type2() {
const pType: any = {firstName: "John", lastName: "Abraham"};
// Set other values
const adrs: any = this.getAddress_Type2(pType);
console.log("Address City Name: ", adrs.cityName);
}
}
const test: PassNReturnMultipleParams = new PassNReturnMultipleParams();
test.check_Type1();
test.check_Type2();
In the above class, there are two functions getAddress_Type1() and getAddress_Type2(), which one is always recommended in Javascript, Typescript world. Both work fine for me.
By my opinion, there shouldn't be usage of "any" in TypeScript. In this case I would recommend you use plain object with interface:
interface PersonType {
firstName: string;
lastName: string;
}
const pType: PersonType = {
firstName: "John",
lastName: "Abraham"
};
This question is pretty opinion-based but you need to take care of some programmatic principles.
Asking for memory efficiency between an javascript object and a class would result to a premature optimization and should be avoided.
You need to ask yourself what do you really need. In your case it will be 'something' that store data as a single instance that you can easily retrieve. If it's just about storing something that you will not frequently manipulate, then a simple object fulfilled all the requirements. Something else would be overengineerd.

Reference equality for container objects

For a specific application we are storing id's of objects in specific classes. For example, a "product" object would have it's string id stored in a "ProductId" object. Likewise a "user" object would have it's string id stored in a UserId object (see example code below).
class Product {
id: ProductId;
price: number;
...
}
class User {
id: UserId;
name: string;
...
constructor(id: UserId, name: string) {
this.id = id;
this.name = name;
...
}
}
class ProductId {
id: string;
constructor(id: string) {
this.id = id;
}
}
class UserId {
id: string;
constructor(id: string) {
this.id = id;
}
}
One issue with this approach is that storing objects in a Map and then trying to retrieve them (see below code) does not work because two UserId's with the same underlying id do not compare equal with ===.
const users = new Map<UserId, User>();
const user = new User(new UserId('8753098'), 'John');
users.set(user.id, user);
console.log(users.get(new UserId('8753098')); //undefined
It seems that javascript does not have operator overloading, or has no way of overriding the equality function.
I have also thought of working with a global map, and create Id's with a static method :
class UserId {
private id: string;
constructor(id: string) {
this.id = id;
}
static userIds = new Map<string, UserId>();
static fromString(id: string) {
let userId = userIds.get(id);
if (userId === undefined) {
userId = new UserId(id);
userIds.set(id, userId);
}
return userId;
}
}
But that has a potential memory leak because all objects are retained in the map and never released.
Does anyone have a solution for this ?
Does anyone have a solution for this ?
Instead of class UserId just do a type type UserId = string.
More
If you are concerned about structural equality and would prefer nominal typing you can add a brand using an enum as shown here
enum UserIdBrand {}
type UserId = UserIdBrand & string;

How do I cast a list of JSON objects into a list of TypeScript objects without losing properties on the TypeScript class?

I have this Customer class:
export class Customer {
id: number;
company: string;
firstName: string;
lastName: string;
name(): string {
if (this.company)
return this.company;
if (this.lastName && this.firstName)
return this.lastName + ", " + this.firstName;
if (this.lastName)
return this.lastName;
if (this.firstName)
return this.firstName;
if (this.id > 0)
return "#" + this.id;
return "New Customer";
}
}
In my controller I pull down a list of customers:
export class CustomersController {
static $inject = ["customerService", "workflowService"];
ready: boolean;
customers: Array<Customer>;
constructor(customerService: CustomerService, workflowService: WorkflowService) {
customerService.getAll().then(
(response) => {
this.customers = response.data;
this.ready = true;
},
() => {
this.ready = true;
}
);
workflowService.uiCustomer.reset();
}
}
angular.module("app")
.controller("CustomersController", ["customerService", "workflowService", CustomersController]);
If it helps, getAll() looks like this:
getAll(): ng.IHttpPromise<Array<Customer>> {
return this.http.get("/api/customers");
}
It's this statement that's causing me grief: this.customers = response.data;
But response.data is strongly typed, so shouldn't it "know" about Customer and name()?
When I do that, of course I am overwriting my strongly typed array with the dumb JSON one, which doesn't have my name() method on it.
So how do I keep my name method without copying every property of every object in the list?
Is this bad design on my part? Having these read-only properties was really common in C#, but I'm a little new to the javascript world. Should I be using a utility class instead?
My current work-around:
this.customers = response.data.map(customer => {
return angular.copy(customer, new Customer());
});
Feels wrong to build a whole new array and copy all those fields (in my real project Customer has many more properties).
Edit: I've found a few related SO questions, such as Mapping JSON Objects to Javascript Objects as mentioned by #xmojmr. My question was specific to TypeScript and I was wondering if TypeScript had any facilities of its own that would generate the javascript to make this a non-issue. If that's not the case, and we're sure TypeScript doesn't aim to solve this class of problem, then we can regard this question as a duplicate.
You're exactly right about what is happening. Typing in typescript mainly provides you with compiler checking. Under the covers, everything compiles to JavaScript which isn't strongly typed.
So, when you say:
getAll(): ng.IHttpPromise<Array<Customer>> {
return this.http.get("/api/customers");
}
all you are really doing is telling is telling the compiler "Hey, I'm pretty sure my api endpoint is going to return an array of Customer objects." But as you know, it really just returns a "dumb JSON" array.
What you could consider doing, is creating an interface that describes the JSON object being returned by the API endpoint. Something like:
interface ICustomer {
id: number;
company: string;
firstName: string;
lastName: string;
}
And then getAll() becomes:
getAll(): ng.IHttpPromise<Array<ICustomer>> {
return this.http.get("/api/customers");
}
Then you could have a class who's constructor takes ICustomer as a parameter. Or you could create a class with a static method that takes ICustomer and returns the "name".
Obviously, what you are doing now works, but I think you're right to be looking for something that better communicates the intent.

Categories