Javascript
Can setter used to validate parameter when creating new object, as seen on this code, the string input on numberOfStudents bypass the setter
class School {
constructor(name, level, numberOfStudents) {
this._name = name;
this._level = level;
this._numberOfStudents = numberOfStudents;
}
get numberOfStudents() {
return this._numberOfStudents;
}
set numberOfStudents(value) {
if (isNaN(value)) {
console.log('Invalid input: numberOfStudents must be set to a Number.');
} else {
return (this._numberOfStudents = value);
}
}
}
const primaryOne = new School('Primary One', 'L1', 'ten');
prints
School { _name: 'Primary One', _level: 'L1', _numberOfStudents: 'ten' }
Please help
You're bypassing the validation because you're assigning to the internal _numberOfStudents property. The setter is only run when you assign to numberOfStudents. So change the constructor to do that:
class School {
constructor(name, level, numberOfStudents) {
this._name = name;
this._level = level;
this.numberOfStudents = numberOfStudents;
}
get numberOfStudents() {
return this._numberOfStudents;
}
set numberOfStudents(value) {
if (isNaN(value)) {
console.log('Invalid input: numberOfStudents must be set to a Number.');
} else {
this._numberOfStudents = value;
}
}
}
const primaryOne = new School('Primary One', 'L1', 'ten');
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
Say I have this "class":
function Car()
{
}
Object.defineProperty(Car.prototype, "Make",
{
get:function() { return this._make; },
set:function(value) { this._make = value; }
});
Object.prototype.Drive = function Drive() { console.log("Car.Drive"); }
Now I want to make a "child class" using prototype inheritance:
function Sedan()
{
}
Sedan.prototype = new Car();
Sedan.prototype.constructor = Sedan;
Sedan.prototype.Drive = function Drive() { Car.prototype.Drive.call(this); console.log("Sedan.Drive"); }
Then I can instantiate a car or a sedan, and drive both. Notice how with sedans, Drive also calls base class (Car) Drive:
var car = new Car(); car.Drive(); var carMake = car.Make;
var sedan = new Sedan(); sedan.Drive(); var sedanMake = sedan.Make;
Is it possible to achieve something similar with properties?
Object.defineProperty(Sedan.prototype, "Make",
{
get: function() { return Car.prototype.Make.<<CALL_GETTER>>(this) + " - Sedan"; },
set: function(value) { Car.prototype.Make.<<CALL_SETTER>>(this, value.replace(" - Sedan", "")); }
});
The only idea I could come up with is something like this:
Car.prototype.get_Make = function get_Make() { return this._make; }
Car.prototype.set_Make = function set_Make(value) { this._make = value; }
Object.defineProperty(Car.prototype, "Make",
{
get:function() { return this.get_Make(); },
set:function(value) { this.set_Make(value); }
});
Then the explicit get_Make and set_Make can be overridden similar to Drive. However, this is clunky. Sure, this boilerplate can be extracted into a helper function which defines the get_ and set_ methods and the property in one shot.
function DefineVirtualProperty(obj, name, getter, setter)
{
obj["get_" + name] = getter;
obj["set_" + name] = setter;
Object.defineProperty(obj, name,
{
get:function() { return this["get_" + name](); },
set: function(value) { this["set_" + name](value); }
});
}
DefineVirtualProperty(Car.prototype, "Make", function() { return this._make; }, function(value) { this._make = value; });
However the overriding still looks a big ugly.
You can use Object.getOwnPropertyDescriptor to get the property descriptor of the parent property.
Then you can use .call() to invoke it, e.g.:
function Car() {}
Object.defineProperty(Car.prototype, "Make", {
get() {
return this._make;
},
set(value) {
this._make = value;
}
});
function Sedan() {}
Sedan.prototype = Object.create(Car);
Sedan.prototype.constructor = Sedan;
Object.defineProperty(Sedan.prototype, "Make", {
get() {
console.log("Sedan Make get");
let desc = Object.getOwnPropertyDescriptor(Car.prototype, "Make");
return desc.get.call(this);
},
set(value) {
console.log("Sedan Make set");
let desc = Object.getOwnPropertyDescriptor(Car.prototype, "Make");
return desc.set.call(this, value);
}
});
let sedan = new Sedan();
sedan.Make = 12;
console.log(sedan.Make);
A few minor tips:
Ideally you should use Object.create for prototype creation, since it doesn't call the constructor when creating the object
Prefer to use Object.defineProperty instead of directly creating properties on the prototype (so you can set enumerable to false)
If you can use ES6 classes this becomes a lot nicer.
You can just use super with them to access the parent property:
class Car {
get Make() {
return this._make;
}
set Make(value) {
this._make = value;
}
}
class Sedan extends Car {
get Make() {
console.log("Sedan Make get");
return super.Make;
}
set Make(value) {
console.log("Sedan Make set");
super.Make = value;
}
}
let sedan = new Sedan();
sedan.Make = 12;
console.log(sedan.Make);
class Vehicle {
constructor (name, type) {
this.name = name;
this.type = type;
console.log(this.constructor.name);
}
getName () {
return this.name;
}
getType () {
return this.type;
}
static create(name, type) {
return new Vehicle(name, type);
}
}
class Car extends Vehicle {
constructor (name) {
super(name, 'car');
}
getName () {
return 'It is a car: ' + super.getName();
}
}
let car = Car.create('Tesla', 'car');
console.log(car.getName()); // It is a car: Tesla
console.log(car.getType()); // car
The above code use ES6 class keyword to define a Vehicle class and a subclass Car from it. How to return Car instance from Vehicle static method.
Try:
let car = new Car('Tesla')
You can pass the ClassName you want to use within your static function create and create an instance from it.
static create(name, type, objClass) {
return new Function(`return new ${objClass ? objClass : 'Vehicle'}('${name}', '${type}');`)();
}
The Function class receives a String with the expression to evaluate, in your case:
new Function(`return new ${objClass}('${name}', '${type}');`)()
Look at this code
class Vehicle {
constructor(name, type) {
this.name = name;
this.type = type;
}
getName() {
return this.name;
}
getType() {
return this.type;
}
static create(name, type, objClass) {
return new Function(`return new ${objClass ? objClass : 'Vehicle'}('${name}', '${type}');`)();
}
}
class Car extends Vehicle {
constructor(name) {
super(name, 'car');
}
getName() {
return 'It is a car: ' + super.getName();
}
}
let car = Car.create('Tesla', 'car', 'Car');
console.log(car.getName()); // It is a car: Tesla
console.log(car.getType()); // car
let superCar = Vehicle.create('Tesla', 'car');
console.log(superCar.getName()); // Tesla
console.log(superCar.getType()); // car
.as-console-wrapper {
max-height: 100% !important
}
See? now is printing the right output.
Resources
Class Function
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
I have a class like this:
class ResponseState() {
constructor(data) {
this.data = data;
}
}
Now I can validate that the prop is of this type:
Container.propTypes = {
myProp: PropTypes.instanceOf(ResponseState).isRequired,
};
This works fine, but how can I validate the type of myProp.data as well? If I use PropTypes.shape, then I cannot check myProp itself.
There is a similar question here, but it does not quite give the answer to this exact problem.
I'm surprised not to see any combining forms for PropTypes.
You could use a custom validator:
Container.propTypes = {
myProp: function(props, propName, componentName) {
if (!propName in props) {
return new Error(propName + " is required");
}
const value = props[propName];
if (!(value instanceof ResponseState)) {
return new Error(propName + " must be an instance of ResponseState");
}
if (/*...validate value.data here ...*/) {
return new Error(propName + " must have etc. etc.");
}
}
};