I want to track changes to the properties of my classes in typescript so that I only update the fields in my database which have actually changed. Currently, I am using an array where I add properties when they change and then I iterate through the array to determine what fields changed and need to be updated in the database. However, I would prefer to do this with some sort of isDirty check. My thought is that I would be able to call something like if (property.dirty) then {} to determine if a property has changed.
I remember being able to do something along these lines in vb.net, but it's been a while and I can't remember exactly what we did in that codebase.
Is the desired code below possible?
Current Code
class test{
private _ID: Guid;
private _dirty: Array<{}>;
get ID(): Guid {
return this._ID;
}
set ID(id: Guid) {
if (this._ID != id) {
this._ID = id;
this._dirty.filter(function (f) { return f.Field == "id" }).length > 0 ? this._dirty.filter(function (f) { return f.Field == "id" })[0].Value = id.toString() : this._dirty.push({Field: "id", Value: id});
}
}
get Name(): string {
return this._Name;
}
set Name(name: string) {
if (this._Name != name) {
this._Name = name;
this._DirtyFields.filter(function (f) { return f.Field == "ccseq_name" }).length > 0 ? this._DirtyFields.filter(function (f) { return f.Field == "ccseq_name" })[0].Value = name : this._DirtyFields.push(new EntityField("ccseq_name", name, FieldType.String));
}
}
}
Desired Code
class test{
private _ID: Guid;
get ID(): Guid {
return this._ID;
}
set ID(id: Guid) {
if (this._ID != id) {
this._ID = id;
this._ID.isDirty = true;
}
}
get Name(): string {
return this._Name;
}
set Name(name: string) {
if (this._Name != name) {
this._Name = name;
this._Name.isDirty = true;
}
}
}
In javascript you can add a property to an object so it's not a problem to do this:
this._ID.dirty = true;
Even when Guid doesn't have this dirty member.
The problem of course is typescript which will complain because of that.
To avoid that you can simply do:
private _ID: Guid & { dirty?: boolean };
Edit
Again, javascript already supports it, you can do this:
obj.dirty = true;
For any js type: booleans, strings, arrays and even functions.
But for having support for that in typescript you can do this:
interface Object {
dirty?: boolean;
}
But be aware that you are adding this to **all* of the objects that you have in your code. As you're not actually changing the prototype it won't have any effect in runtime, but typescript-wise it will effect all instances.
The way I solved this was to create a Field class that I then used as properties in my Objects.
Field Class
export class EntityField {
private _Field: string;
private _Value: any;
private _FType: FieldType;
private _isDirty: boolean;
constructor(field: string, value: any, fType: FieldType) {
this._Field = field;
this._Value = value;
this._FType = fType;
this._isDirty = false;
}
markClean(): void {
this._isDirty = false;
}
markDirty(): void {
this._isDirty = true;
}
get isDirty(): boolean {
return this._isDirty;
}
get Field(): string {
return this._Field;
}
set Field(field) {
if (this._Field !== field) {
this._Field = field;
}
}
get Value(): any {
return this._Value;
}
set Value(value: any) {
if (this._Value !== value) {
this._Value = value;
this._isDirty = true;
}
}
get FType(): FieldType {
return this._FType;
}
set FType(fType: FieldType) {
if (this._FType != fType) {
this._FType = fType;
}
}
}
Usage
export class Entity{
public Name: Field
}
Entity test = new Entity()
Entity.Name.isDirty() // Returns False
Entity.Name.Value = "Test";
Entity.Name.isDirty() // Returns True
Related
The server has a Meteor Method that returns a GiftList object that contains a Gift set.
The client has a Meteor Call that prints out the result. The Gift set is undefined even though it is initialised and sent by the server. The instance variables don't seem to be included in the response even though the server has sent it.
Gift List
import {Gift} from "../gift/Gift";
export class GiftList {
private _id: number;
private _personName:string;
private _user: User;
private _gifts: Set<Gift>;
get id(): number {
return this._id;
}
set id(value: number) {
this._id = value;
}
get personName(): string {
return this._personName;
}
set personName(value: string) {
this._personName = value;
}
get user(): User {
return this._user;
}
set user(value: User) {
this._user = value;
}
get gifts(): Set<Gift> {
return this._gifts;
}
set gifts(value: Set<Gift>) {
this._gifts = value;
}
}
Gift
import {GiftList} from "../giftlist/GiftList";
export class Gift {
private _id: number;
private _name: string;
private _description: string;
private _isPrivate: boolean;
private _cost: number;
private _giftList: GiftList;
get id(): number {
return this._id;
}
set id(value: number) {
this._id = value;
}
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
get description(): string {
return this._description;
}
set description(value: string) {
this._description = value;
}
get isPrivate(): boolean {
return this._isPrivate;
}
set isPrivate(value: boolean) {
this._isPrivate = value;
}
get cost(): number {
return this._cost;
}
set cost(value: number) {
this._cost = value;
}
get giftList(): GiftList {
return this._giftList;
}
set giftList(value: GiftList) {
this._giftList = value;
}
}
Server - Meteor Method
Meteor.methods({
"getGiftLists": function (): GiftList[] {
const giftList: GiftList = new GiftList();
giftList.gifts = new Set();
const gift: Gift = new Gift();
gift.name= "Example gift";
gift.description = "Description of gift";
giftList.gifts.add(gift);
// I've printed the value here and the gift list definitely contains gifts as expected.
return [giftList]
}
})
Client - Meteor Call
Meteor.call("getGiftLists", {}, (err: any, res: GiftList[]) => {
if (err) {
alert(err);
} else {
console.dir(res); // Defined
console.log(res.length) // 1
console.dir(res[0].gifts); // Undefined
callback(res);
}
});
Question
Why is the Gift set undefined?
I believe the problem here is that Metoer's EJSON doesn't know how to serialize a Set to be sent to the client. EJSON provides a way to define new types and how they should be serialized and de-serialized. Have a look at the EJSON docs.
https://docs.meteor.com/api/ejson.html
I'm trying to implement binary search tree in Type Script using generic types. However I have an issue with adding child in node, because there is an error when parent.leftChild(newNode) - "Cannot invoke an expression whose type is lacks a call signature. Type Node has no compatible call signatures."
export { };
class Node <T> {
private _key: number;
private _data: T;
private _leftChild: Node <T>;
private _rightChild: Node <T>;
constructor(key: number, data: T) {
this._key = key;
this._data = data;
}
get key(): number {
return this._key;
}
get data(): T {
return this._data;
}
get leftChild(): Node <T> {
return this._leftChild;
}
get rightChild(): Node <T> {
return this._rightChild;
}
set leftChild(child: Node <T>) {
this._leftChild = child;
}
set rightChild(child: Node <T>) {
this._rightChild = child;
}
}
class BinaryTree <H> {
private _root: Node<H>;
public addNode(key: number, data: H): void {
const newNode: Node <H> = new Node <H> (key, data);
if (this._root == null) {
this._root = newNode;
} else {
let focusNode: Node <H> = this._root;
let parent: Node <H>;
while (true) {
parent = focusNode;
if (key < focusNode.key) {
focusNode = focusNode.leftChild;
if (focusNode == null) {
parent.leftChild(newNode);
return;
}
}
}
}
}
}
Using Angular & TypeScript, we can use generics and all the Compile-goodness to assure some sort of type-safety. But if we are using for example the HTTP-Service, we don't get a specific objec but just parsed JSON. For example, we have some generic methods doing that:
public get<T>(relativeUrl: string): Promise<T> {
const completeUrlPromise = this.createCompleteUrl(relativeUrl);
const requestOptions = this.createRequestOptions(ContentType.ApplicationJson, true);
return completeUrlPromise.then(completeUrl => {
return this.processResponse<T>(this.http.get(completeUrl, requestOptions));
});
}
private processResponse<T>(response: Observable<Response>): Promise<T> {
const mappedResult = response.map(this.extractData);
const result = mappedResult.toPromise();
return result;
}
private extractData(res: Response): any {
let body;
if (!Array.isArray(res)) {
if (res.text()) {
body = res.json();
}
} else {
body = res;
}
if (!JsObjUtilities.isNullOrUndefined(body)) {
return body;
}
return {};
}
Ultimately, the generic type is useless this way, since we just get the JSON. If the generic object has methods or properties not in the JSON, they are lost.
To avoid this, we added the possibility to pass a constructor-function to truly create the object:
private processResponse<T>(response: Observable<Response>, ctor: IParameterlessConstructor<T> | null = null): Promise<T> {
let mappedResult = response.map(this.extractData);
if (ctor) {
mappedResult = mappedResult.map(f => {
const newObj = JsObjFactory.create(f, ctor);
return newObj;
});
}
const result = mappedResult.toPromise();
return result;
}
And the JsObjFactory looking like this:
export class JsObjFactory {
public static create<T>(source: any, ctorFn: IParameterlessConstructor<T>): T {
const result = new ctorFn();
this.mapDefinedProperties(source, result);
return result;
}
private static mapDefinedProperties<T>(source: Object, target: T): void {
const properties = Object.getOwnPropertyNames(target);
properties.forEach(propKey => {
if (source.hasOwnProperty(propKey)) {
target[propKey] = source[propKey];
}
});
}
}
This works well for shallow objects, but doesn't work, if a property is also a complex type with a constructor. As there are no types at runtime, the best bet I have currently is to kindahow parse the properties, check if classes exist and then create them. But this seems to be very error-prone and cumbersome.
Since I'm always certain, I'm not the only person with this issues, are there solutions, or TypeScript/JavaScript features I'm not aware off, which would help here?
I don't personally do it like this, but it may be what you're looking for.
Example:
Customer.ts
export interface ICustomer {
Id: number;
Name: string;
Orders: IOrder[];
...
}
export class Customer implements ICustomer {
public Id: number;
public Name: string;
public Orders: IOrder[];
constructor(customer: Partial<ICustomer>) {
this.Id = customer.Id || 0;
this.Name = customer.Name || '';
this.Orders = [];
customer.Orders.forEach((order: IOrder) => this.Orders.push(new Order(order)));
}
//some functions
}
Order.ts
export interface IOrder {
Id: number;
Weight: number;
Shipmentdate: string;
}
export class Order implements IOrder {
public Id: number;
public Weight: number;
public Shipmentdate: string;
constructor(order: Partial<IOrder>) {
this.Id = order.Id || 0;
this.Weight = order.Weight || 0;
this.Shipmentdate = order.Shipmentdate || '';
}
//some functions
}
This would make the Object (in this case Customer) responsible for instantiating it's known complex types that you pass in. And Order in turn could have its complex types that it instantiates.
I have the following class structure:
export abstract class PersonBase {
public toJSON(): string {
let obj = Object.assign(this);
let keys = Object.keys(this.constructor.prototype);
obj.toJSON = undefined;
return JSON.stringify(obj, keys);
}
}
export class Person extends PersonBase {
private readonly _firstName: string;
private readonly _lastName: string;
public constructor(firstName: string, lastName: string) {
this._firstName = firstName;
this._lastName = lastName;
}
public get first_name(): string {
return this._firstName;
}
public get last_name(): string {
return this._lastName;
}
}
export class DetailPerson extends Person {
private _address: string;
public constructor(firstName: string, lastName: string) {
super(firstName, lastName);
}
public get address(): string {
return this._address;
}
public set address(addy: string) {
this._address = addy;
}
}
I am trying to get toJSON to output all the getters (excluding private properties) from the full object hierarchy.
So if I have a DetailPerson instance and I call the toJSON method, I want to see the following output:
{
"address": "Some Address",
"first_name": "My first name",
"last_name": "My last name"
}
I used one of the solutions from this Q&A but it doesn't solve my particular use case - I am not getting all the getters in the output.
What do I need to change here to get the result I am looking for?
The link you provided uses Object.keys which leaves out properties on the prototype.
You could use for...in instead of Object.keys:
public toJSON(): string {
let obj: any = {};
for (let key in this) {
if (key[0] !== '_') {
obj[key] = this[key];
}
}
return JSON.stringify(obj);
}
Edit: This is my attempt to return only getters, recursively, without assuming that non-getters start with underscores. I'm sure there are gotchas I missed (circular references, issues with certain types), but it's a good start:
abstract class PersonBase {
public toJSON(): string {
return JSON.stringify(this._onlyGetters(this));
}
private _onlyGetters(obj: any): any {
// Gotchas: types for which typeof returns "object"
if (obj === null || obj instanceof Array || obj instanceof Date) {
return obj;
}
let onlyGetters: any = {};
// Iterate over each property for this object and its prototypes. We'll get each
// property only once regardless of how many times it exists on parent prototypes.
for (let key in obj) {
let proto = obj;
// Check getOwnPropertyDescriptor to see if the property is a getter. It will only
// return the descriptor for properties on this object (not prototypes), so we have
// to walk the prototype chain.
while (proto) {
let descriptor = Object.getOwnPropertyDescriptor(proto, key);
if (descriptor && descriptor.get) {
// Access the getter on the original object (not proto), because while the getter
// may be defined on proto, we want the property it gets to be the one from the
// lowest level
let val = obj[key];
if (typeof val === 'object') {
onlyGetters[key] = this._onlyGetters(val);
} else {
onlyGetters[key] = val;
}
proto = null;
} else {
proto = Object.getPrototypeOf(proto);
}
}
}
return onlyGetters;
}
}
I've the following class:
export class Test {
private _rowsCount: string;
public get RowsCount(): string {
return this._rowsCount;
};
public set RowsCount(value: string) {
this._rowsCount = value;
};
private _rowsCount2: string;
public get RowsCount2(): string {
return this._rowsCount2;
};
public set RowsCount2(value: string) {
this._rowsCount2 = value;
};
}
I need to iterate over the properties in a specific class, I tried the following:
Object.keys(this).forEach((key)=> {
console.log(key);
});
But the problem that this iterate just over the private fields, I tried also the following I got all the methods and properties:
for (var property in this) {
if (this.hasOwnProperty(property)) {
console.log(property);
}
}
Does anyone have a solution?
Thanks!
If you need to only get the getters/setters, then you'll need to do something like:
class Test {
...
public static getGetters(): string[] {
return Object.keys(this.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.prototype, name)["get"] === "function"
});
}
public static getSetters(): string[] {
return Object.keys(this.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.prototype, name)["set"] === "function"
});
}
}
Test.getGetters(); // ["RowsCount", "RowsCount2"]
Test.getSetters(); // ["RowsCount", "RowsCount2"]
(code in playground)
You can put the static methods in a base class, and then when you extend it the subclass will have those static methods as well:
class Base {
public static getGetters(): string[] {
return Object.keys(this.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.prototype, name)["get"] === "function"
});
}
public static getSetters(): string[] {
return Object.keys(this.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.prototype, name)["set"] === "function"
});
}
}
class Test extends Base {
...
}
Test.getGetters(); // work the same
(code in playground)
If you want these methods to be instance methods then you can do this:
class Base {
public getGetters(): string[] {
return Object.keys(this.constructor.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.constructor.prototype, name)["get"] === "function"
});
}
public getSetters(): string[] {
return Object.keys(this.constructor.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.constructor.prototype, name)["set"] === "function"
});
}
}
The change is that instead of using this.prototype you're using this.constructor.prototype.
Then you simply:
let a = new Test();
a.getGetters(); // ["RowsCount", "RowsCount2"]
(code in playground)
Edit
Based on a comment by #Twois, who pointed out that it won't work when targetting es6, here's a version that will work:
class Base {
public static getGetters(): string[] {
return Reflect.ownKeys(this.prototype).filter(name => {
return typeof Reflect.getOwnPropertyDescriptor(this.prototype, name)["get"] === "function";
}) as string[];
}
public static getSetters(): string[] {
return Reflect.ownKeys(this.prototype).filter(name => {
return typeof Reflect.getOwnPropertyDescriptor(this.prototype, name)["set"] === "function";
}) as string[];
}
}
The main difference: using Reflect.ownKeys(this.prototype) instead of Object.keys(this.prototype).
What you can do is for the class you want to use it extend the class above and make the properties public for this reason;
class TestExposed extend Test {
public _rowsCount: string;
public _rowsCount2: string;
}
And in your Test class make the private protected:
class Test {
protected _rowsCount: string;
public get RowsCount(): string {
return this._rowsCount;
};
public set RowsCount(value: string) {
this._rowsCount = value;
};
protected _rowsCount2: string;
public get RowsCount2(): string {
return this._rowsCount2;
};
public set RowsCount2(value: string) {
this._rowsCount2 = value;
};
}
Then you should be able to iterate over the properties in an external class;
But if you want to have the values; Why not make a function that exposes the values by returning them in an array or log them as a string;