I am trying to define my data structure to encapsulate the logic for working with data in one class. I then have the Field class that has this information.
Field<T>(private name:string, private size:number, private comment:string) {
}
get Name():string {
return this.name;
}
get Size():number {
return this.size;
}
Create a Field reference:
class User {
public FirstName:Field<string> = new Field<string>('FirstName', 20);
public LastName:Field<string> = new Field<string>('LastName', 32);
public Age:Field<number> = new Field<number>('Age', 3);
}
The above means I have a data field 'FirstName' which is a string and maximum of 20 characters. 'LastName' is a 32 character string and Age can be 3 digits.
I use this to then be able to validate my data on a form (using the Size for instance to limit the number of characters that could be entered) as well as when reading from an external API.
If I am getting the data from an external API, I can also use the field 'Size' to limit the amount of data I copy into the field, so as to truncate the data.
This allows my data interface layer to work with a data type class, expecting all fields to be Field<T>
, and then allows me to use my library functions to control the data size, add data validation to forms etc., without always having to write the validate functions in the HTML, since I can use loops in Angular and extract the information from the data structure.
My question now is how to get a generic interface for working with data coming from lists and objects in AngularFire.
Normally, when accessing data and the structure from AngularFire, I can use:
constructor(public afDb:AngularFireDatabase) {
...
this.afDb.object<IUser>('/user/1').valueChanges();
...
}
This would get the data and automatically parse it into the IUser interface (not shown above).
I want to be able to generate an IUser interface from my User class which has the data structures in the Field<T>
. Basically I want to generate an interface from the User class something like:
export interface IUser {
FirstName?:string;
LastName?:string;
Age?:number;
}
Then this interface could be used in my access to AngularFire. The other option/question would be how to do the equivalent of the afDb.object<IUser>...
and leave the <IUser>
off, but be able to parse the results from the AngularFire object into my data structure which is the User class. So the parsing would call Field<T>.SetValue();
or something.
If you want to build a totally generic interface to handle all different objects with multiple Field<T>
s then you should probably be looking at creating something like this:
class Field<T> {
constructor(private name: string, private size: number) {
}
}
interface IField {
[index: string]: Field<string | number>;
}
interface IWhatever {
fields: ArrayLike<IField>;
}
That way you can use the IWhatever
with afDb.object<IWhatever>()
and get back an IWhatever
that has an Array
of objects with type IField
, which is an interface that is very generic and can hold any amount of named properties, each of wich with a value of the concrete types Field<string>
or Field<number>
(you can expand on those types too as you require).
Is this what you are looking for?
-- UPDATE with more helpful guidance --
After reading your comments and reviewing again your answer I think that I now understand better what you might need. Take a look at the next example, which I think is more suitable to what you want to do. I commented each class and interface to make it clearer to understand the whole concept. Let me know if you need more clarification and if it helped.
// This is a implementation class of our concept of "Field" objects.
// The T is a generic type, which means that it can be any type every time
// you instantiate a new object of this class.
// the type will be used to define the type of the `value` variable
// that will hold the actual internal value of the "Field".
//
// So when you create a new Field<string>(....) you will always know
// that the `value` property will be of type `string` and not anything else.
class Field<T> {
public title: string;
public maxLength: number;
public value: T;
constructor(title: string, maxLength: number) {
this.title = title;
this.maxLength = maxLength;
}
}
// this is an interface, that defines an object with any number
// of properties of type Field<string> or Field<number>
interface IObject {
// can have any number of Field typed properties
[index: string]: Field<string | number>;
}
// this is a more specific version of the above interface, that
// actually defines exactly which fields and their types it
// should have and which ones should be required or optional
interface IUser {
// required fields
firstName: Field<string>;
lastName: Field<string>;
age: Field<number>;
// lets define an optional one
favoriteColor?: Field<string>;
}
// Suppose that we get a data structure from somewhere that has no type
// and that we want to encapsulate it inside one of our interfaces
// in order to make it easier to work with it
//
// So lets create a literal object with no specific type (a data structure):
let data = {
firstName: new Field<string>('First Name', 20),
lastName: new Field<string>('Last Name', 32),
age: new Field<number>('Age', 3),
}
// we can then assign this object or data structure to a
// variable with the very generic IObject as type
let anObjectOfUnknownType: IObject = data;
// no problem, no complaints from typescript, the data is compatible
// with our IObject interface!
// We can also assign it to a variable
// of type IUser, which is more specific
// and narrows down our data structure to the definition that
// we the IUser interface expects it to be. For this to work
// though we must make sure that the structure must satisfy
// and be compatible with the IUser interface.
let userDataStructure: IUser = data;
// and yes, it works, because the data is compatible with IUser
// We now have encapsulated the "unknown" data structure in a
// useful typed variable from which we can access its properties
// and enjoy type checking and intellisense
console.log("user's full name is: " + userDataStructure.firstName.value + " " + userDataStructure.lastName.value);
console.log("user's age is: " + userDataStructure.age);
if (typeof userDataStructure.favoriteColor !== "undefined") {
console.log("user's favorite color is: " + userDataStructure.favoriteColor.value);
}