Generic User Collection - ldco2016/microurb_web_framework GitHub Wiki
So I need to turn my Collection
class into a generic class and essentially remove every reference to User
from it:
import axios, { AxiosResponse } from "axios";
import { User, UserProps } from "./User";
import { Eventing } from "./Eventing";
export class Collection {
models: User[] = [];
events: Eventing = new Eventing();
constructor(public rootUrl: string) {}
get on() {
return this.events.on;
}
get trigger() {
return this.events.trigger;
}
fetch(): void {
axios.get(this.rootUrl).then((response: AxiosResponse) => {
response.data.forEach((value: UserProps) => {
const user = User.buildUser(value);
this.models.push(user);
});
this.trigger("change");
});
}
}
If we have any reference to User
or UserPropos
inside of here that means I will not be able to use the class of Collection
for other types of data inside my application as well.
So after removing the import statement for the User
model then I will classify Collection
as a generic class like so:
import axios, { AxiosResponse } from "axios";
import { Eventing } from "./Eventing";
export class Collection<T> {
models: User[] = [];
events: Eventing = new Eventing();
constructor(public rootUrl: string) {}
get on() {
return this.events.on;
}
get trigger() {
return this.events.trigger;
}
fetch(): void {
axios.get(this.rootUrl).then((response: AxiosResponse) => {
response.data.forEach((value: UserProps) => {
const user = User.buildUser(value);
this.models.push(user);
});
this.trigger("change");
});
}
}
So T
can represent the User
class, but in some future it could also represent a blog post, a class image or comment or whatever else. T
is essentially the type of whatever collection of things I have stored inside of here.
So I will say that models
is now an element that contains a type of T
like so:
import axios, { AxiosResponse } from "axios";
import { Eventing } from "./Eventing";
export class Collection<T> {
models: T[] = [];
events: Eventing = new Eventing();
constructor(public rootUrl: string) {}
get on() {
return this.events.on;
}
get trigger() {
return this.events.trigger;
}
fetch(): void {
axios.get(this.rootUrl).then((response: AxiosResponse) => {
response.data.forEach((value: UserProps) => {
const user = User.buildUser(value);
this.models.push(user);
});
this.trigger("change");
});
}
}
I now have a reference to UserProps
I need to replace as well. This is a troubling note because right now type T
is a User
class and the User
class doesn't have a reference to type UserProps
. So I need to essentially add a second generic type to my class to allow me to specify the type of UserProps
. To do so, I can just add on a second generic type at the top like so:
import axios, { AxiosResponse } from "axios";
import { Eventing } from "./Eventing";
export class Collection<T, K> {
models: T[] = [];
events: Eventing = new Eventing();
constructor(public rootUrl: string) {}
get on() {
return this.events.on;
}
get trigger() {
return this.events.trigger;
}
fetch(): void {
axios.get(this.rootUrl).then((response: AxiosResponse) => {
response.data.forEach((value: UserProps) => {
const user = User.buildUser(value);
this.models.push(user);
});
this.trigger("change");
});
}
}
With functions in general, we can pass in one, two or more arguments, same thing with generics.
So all I am doing is saying whenever I refer to Collection
I am going to pass in a type for T
and type for K
as well.
In this case, K
will be the type that specifies the structure of JSON data that I get back like so:
import axios, { AxiosResponse } from "axios";
import { Eventing } from "./Eventing";
export class Collection<T, K> {
models: T[] = [];
events: Eventing = new Eventing();
constructor(public rootUrl: string) {}
get on() {
return this.events.on;
}
get trigger() {
return this.events.trigger;
}
fetch(): void {
axios.get(this.rootUrl).then((response: AxiosResponse) => {
response.data.forEach((value: K) => {
const user = User.buildUser(value);
this.models.push(user);
});
this.trigger("change");
});
}
}
The next big issue is that I have a direct reference to a function that is going to attempt to create a user out of some JSON data. I cannot replace it with a generic because User
is an actual class, an actual object, not a type and I am attempting to call an actual function.
T
and K
only goes in locations where I am attempting to specify a type.
To solve this, I need to pass in a function to be used to take some JSON data and return an instance of a model like so:
import axios, { AxiosResponse } from "axios";
import { Eventing } from "./Eventing";
export class Collection<T, K> {
models: T[] = [];
events: Eventing = new Eventing();
constructor(public rootUrl: string, public deserialize:) {}
get on() {
return this.events.on;
}
get trigger() {
return this.events.trigger;
}
fetch(): void {
axios.get(this.rootUrl).then((response: AxiosResponse) => {
response.data.forEach((value: K) => {
const user = User.buildUser(value);
this.models.push(user);
});
this.trigger("change");
});
}
}
The deserialize
function is going to take in some JSON data and turn it into an actual instance of an object and I will pass it to my collection whenever I create an instance of it. This will allow me to reuse the collection for any type of data I want.
The type of deserialize
is going to be a function that is going to take in some JSON and the JSON will be of type K
because essentially K
is the UserProps
with the name, id and age. So this would look like so:
import axios, { AxiosResponse } from "axios";
import { Eventing } from "./Eventing";
export class Collection<T, K> {
models: T[] = [];
events: Eventing = new Eventing();
constructor(public rootUrl: string, public deserialize: (json: K) =>) {}
get on() {
return this.events.on;
}
get trigger() {
return this.events.trigger;
}
fetch(): void {
axios.get(this.rootUrl).then((response: AxiosResponse) => {
response.data.forEach((value: K) => {
const user = User.buildUser(value);
this.models.push(user);
});
this.trigger("change");
});
}
}
So its going to take an argument of type K
and return something of type T
, which is the actual thing I am storing inside the models
array like so:
import axios, { AxiosResponse } from "axios";
import { Eventing } from "./Eventing";
export class Collection<T, K> {
models: T[] = [];
events: Eventing = new Eventing();
constructor(public rootUrl: string, public deserialize: (json: K) => T) {}
get on() {
return this.events.on;
}
get trigger() {
return this.events.trigger;
}
fetch(): void {
axios.get(this.rootUrl).then((response: AxiosResponse) => {
response.data.forEach((value: K) => {
const user = User.buildUser(value);
this.models.push(user);
});
this.trigger("change");
});
}
}
Now I need to ensure I call deserialize
with each piece of json
record and get back the instance of the model. To do so, I deleted the entire const user
line and pushed in this.deserialize(value)
like so:
import axios, { AxiosResponse } from "axios";
import { Eventing } from "./Eventing";
export class Collection<T, K> {
models: T[] = [];
events: Eventing = new Eventing();
constructor(public rootUrl: string, public deserialize: (json: K) => T) {}
get on() {
return this.events.on;
}
get trigger() {
return this.events.trigger;
}
fetch(): void {
axios.get(this.rootUrl).then((response: AxiosResponse) => {
response.data.forEach((value: K) => {
this.models.push(this.deserialize(value));
});
this.trigger("change");
});
}
}
I am going to stick in the json
data I get. I should get back an instance of a model that I am going to add to my array of models.
Now inside of my index.ts
file, I have an error around my new Collection()
:
import { Collection } from "./models/Collection";
const collection = new Collection("http://localhost:3000/users");
collection.on("change", () => {
console.log(collection);
});
collection.fetch();
The first thing I have to do is provide some types for the Collection
itself and the first type will be a reference to the User
class like so:
import { User, UserProps } from "./models/User";
import { Collection } from "./models/Collection";
const collection = new Collection<User>("http://localhost:3000/users");
collection.on("change", () => {
console.log(collection);
});
collection.fetch();
The second argument will be a description to the array of json objects I get back:
import { User, UserProps } from "./models/User";
import { Collection } from "./models/Collection";
const collection = new Collection<User, UserProps>(
"http://localhost:3000/users"
);
collection.on("change", () => {
console.log(collection);
});
collection.fetch();
So the first time is the class that I will eventually produce, which is User
and the second is going to be the description of the array of json objects I get back from the API which is UserProps
. Then as the second argument I have to pass in a function that takes in an argument of structure UserProps
and return an instance of a user like so:
import { User, UserProps } from "./models/User";
import { Collection } from "./models/Collection";
const collection = new Collection<User, UserProps>(
"http://localhost:3000/users",
(json: UserProps) => User.buildUser(json)
);
collection.on("change", () => {
console.log(collection);
});
collection.fetch();
So heading over to my browser again I will do a quick test:
So I got the collection again and I still have inside my models property an array of records where everyone of them is an instance of a user that has the appropriate data attached to it.
Now the problem here is that I probably do not want to do this customization, every single time that I have to create a new collection of users. Every single time I create a collection of users, I don't want to have to pass in this url:
const collection = new Collection<User, UserProps>(
"http://localhost:3000/users",
(json: UserProps) => User.buildUser(json)
);
And I dont want to have to define the deserialize
function:
(json: UserProps) => User.buildUser(json)
So I think I need to figure out a better place to put this code than inside my index.ts
file.