I am trying to create and use some Data Classes in NodeJs which i defined in Typescript and are at a point where i am wondering if there is a simpler way.
In javascript i was able to do
let myBuilding = new Building
Then i was able to just do
myBuilding.col1 = "Wall"
myBuilding.col2 = "None"
and so on
in typescript it doesn't like it if i don't declare everything at the point of declaration. Is there a way to initialize a class with blank values and then assign them later ? Also what happens when there is something that doesnt get a value assigned ? in javascript we dont get that item returned which is great when parsing from json to a class
Here is what a class of mine looks like
export class Exterior {
public exterior: string;
public fencing: string;
public security: string;
public sewer: string;
public lot: string;
public pool: string;
public patioPorch: string;
public spa: string;
constructor(exterior: string, fencing: string, security: string, sewer: string, lot: string, pool: string,
patioPorch: string, spa: string) {
this.exterior = exterior;
this.fencing = fencing;
this.security = security;
this.sewer = sewer;
this.lot = lot;
this.pool = pool;
this.patioPorch = patioPorch;
this.spa = spa;
}
}
when you declare a type you can just make things optional:
class Building {
height?: number;
}
now typescript won't complain if you don't declare a height right away but you still can't add extra undeclared fields like width.
You can also declare things as a Partial<Building> if they meet some subset of the interface but not all of the required fields.
Here are four ways of achieving this:
class Foo {
// This will not do anything, so remove it
constructor() {}
// this will be undefined initially
private internalA!: number;
public get fieldA() {
return this.internalA
}
public set fieldA(internalAValue: number) {
this.internalA = internalAValue;
}
// this will be undefined initially
public fieldB!: boolean;
// this will be undefined initially
public fieldC!: string;
// this will be "example-initial-value" initially
public fieldD: string = "example-initial-value";
}
const foo = new Foo();
// Method 1 using setters
foo.fieldA = 2;
alert(foo.fieldA);
// Method 2 using simple assigning
foo.fieldB = true;
alert(foo.fieldB);
// Method 3 using Object.defineProperty
Object.defineProperty(foo, 'fieldC', {
value: "test",
writable: false
});
alert(foo.fieldC);
// Method 4 using Object.assign
Object.assign(foo, {fieldD: "hello"});
alert(foo.fieldD);
Be very careful or even avoid Object.defineProperty and Object.assign directly without creating a wrapper that enforces the types. They both have many ways of getting around / forgetting your type system.
setter method and direct public field assignment are the easiest type safe ways.
You can run it here
Here is a way for setting multiple things in one go without initialising first
interface IFooParams {
fieldA: number;
fieldB: boolean;
fieldC: string;
fieldD: string
}
class Foo {
// this will be undefined initially
public fieldA!: number;
// this will be undefined initially
public fieldB!: boolean;
// this will be undefined initially
public fieldC!: string;
// this will be "example-initial-value" initially
public fieldD: string = "example-initial-value";
public setAllInOneGo(params: IFooParams): void {
this.fieldA = params.fieldA;
this.fieldB = params.fieldB;
this.fieldC = params.fieldC;
this.fieldD = params.fieldD;
}
}
const foo = new Foo();
// Whenever:
foo.setAllInOneGo({
fieldA: 2,
fieldB: false,
fieldC: "hello",
fieldD: "world"
});
Related
I need to dynamically update the fields of this class dynamically with an object
export default class Foo {
private accessKey: string;
private workspaceId: string;
private api: AxiosInstance;
public bar: string;
public name: string;
...
...
private async fetch() {
try {
// data contains bar and name value
const { data } = await this.api.get("/");
// goal
this = {...this, ...data};
See goal comment, how can I do this dynamically?
Assignments and this
Why assigning to this is disallowed
Disregarding that it's not allowed, you don't want to reassign your this reference.
If it was allowed, we could write this confusing code:
const object = {
reassignSelf() {
this = {};
}
};
const ref = object;
object.reassignSelf();
/* Both ref and object are constant variables,
* so their values (references) should never change.
* But by reassigning `this` in object.reassignSelf,
* what value should this comparison produce?
*/
console.log(ref === object);
Then how to assign to this?
As implied earlier, we don't want to reassign this; we want to reassign its properties:
Static assignment. Example:
this.bar = data.bar;
this.name = data.name;
Dynamic assignment. Example:
Object.assign(this, data);
Use Object.assign
class X {
update(from: Partial<this>) {
Object.assign(this)
}
}
My issue is I don't know how to fix this. I've looked on other questions similar on this site, and none of them helped.
I have this d.ts:
declare module prompto {
export type PromptoOptions = {
showPreview: boolean,
fileName: string,
outputPath: string,
components: QOptions[]
}
export type QOptions = {
name: string,
type: number
}
export class Prompto {
public addComponents(data: Array<QOptions>): void;
public run(): void;
public showPreview: boolean;
public outputPath: string;
public fileName: string;
public components: QOptions[];
}
}
this is the class:
import PromptoOptions = prompto.PromptoOptions;
import { run } from "../functions/run";
import QOptions = prompto.QOptions;
export class Prompto extends prompto.Prompto {
constructor(options: PromptoOptions = {
showPreview: false,
outputPath: "./",
fileName: "output",
components: []
}) {
super();
this.showPreview = options.showPreview
this.fileName = options.fileName
this.outputPath = options.outputPath
this.components = [];
}
addComponents(arr: Array<QOptions>) {
for (const item of arr) {
if (!item.name || !item.type) {
throw new TypeError("Invalid item")
}
this.components.push(item)
}
}
run() {
console.log(this)
run({ components: this.components })
}
}
and the full error:
src/structures/prompto.ts:17:14 - error TS2551: Property 'components' does not exist on type 'Prompto'. Did you mean 'addComponents'?
17 this.components = [];
~~~~~~~~~~
src/structures/prompto.ts:20:5
20 addComponents(arr: Array<QOptions>) {
~~~~~~~~~~~~~
'addComponents' is declared here.
I just want to fix this. I've been trying to fix this for like 7 hours now. Everywhere I ask no one helps. Just please.
I played around with this in the TS playground and I think that a fix will be to add the line
public components: QOptions[]; before the constructor. Could use a different access modifier (private or protected) if you wanted.
However, I'll also say that I've never seen syntax where you use import with = and in all honesty I've got no idea what effects that might be having on your code.
For trouble shooting this, personally, I would remove the imports and manually write the types I'm expecting to use in the .ts file to check the class is behaving as I want. Then I'd remove those types and import them to check that the imports are behaving as I want.
I am trying to develop a chaincode for Hyperledger Fabric 1.4 using the IBM Blockchain Platform plugin for Visual Studio Code and the fabric-contract-api v1.4.2. In this situation, I am facing some problems when trying to use interfaces from my chaincode methods. This is the error:
Error: Type not properly specified for parameter myAssetConfig, can not process pure Object types
The asset I am using is called MyAsset. This is the declaration of that element:
#Object()
export class MyAsset {
#Property()
public propertyA: string;
#Property()
public propertyB: string;
#Property()
public propertyC?: string;
constructor(myAssetConfig: IMyAssetConfig) {
this.propertyA = myAssetConfig.propertyA;
this.propertyB = myAssetConfig.propertyB;
if (myAssetConfig.hasOwnProperty('propertyC')) {
this.propertyC = myAssetConfig.propertyC;
}
}
}
Apart from it, this the content of types/index.d.ts (I am using the flag #Object here but I am not exactly sure if I should and why/why not):
#Object
export interface IMyAssetConfig {
propertyA: string;
propertyB: string;
propertyC?: string;
}
export type MyAssetId = string;
Finally, this is the content of myasset-contract.ts
#Info({title: 'MyAssetContract', description: 'My MyAsset Contract'})
export class MyAssetContract extends Contract {
#Transaction(false)
#Returns('boolean')
public async myAssetExists(ctx: Context, myAssetId: MyAssetId): Promise<boolean> {
const buffer = await ctx.stub.getState(myAssetId);
return (!!buffer && buffer.length > 0);
}
#Transaction()
public async createMyAsset(ctx: Context, myAssetConfig: IMyAssetConfig): Promise<MyAssetId> {
const myAssetId: MyAssetId = myAssetConfig.shippingCompanyId + '-' + this.generateInternMyAssetId(ctx);
const exists = await this.myAssetExists(ctx, myAssetId);
if (exists) {
throw new Error(`The myAsset ${myAssetId} already exists`);
}
const myAsset = new MyAsset(myAssetConfig);
const buffer = Buffer.from(JSON.stringify(myAsset));
await ctx.stub.putState(myAssetId, buffer);
return myAssetId;
}
#Transaction(false)
#Returns('MyAsset')
public async readMyAsset(ctx: Context, myAssetId: MyAssetId): Promise<MyAsset> {
const exists = await this.myAssetExists(ctx, myAssetId);
if (!exists) {
throw new Error(`The myAsset ${myAssetId} does not exist`);
}
const buffer = await ctx.stub.getState(myAssetId);
return JSON.parse(buffer.toString()) as MyAsset;
}
#Transaction()
public async splitMyAsset(ctx: Context, myAssetId: MyAssetId, children: IMyAssetConfig[]): Promise<MyAssetId[]> {
// REMOVED because it is actually irrelevant to the problem and makes the post too long.
return [];
}
}
Of course, this is all anonymized and reduced but I think the problem is clear enough. I can not use IMyAssetConfig as I type for the parameter myAssetConfig but there is no problem if I use string. I could understand till some point that fabric-contract-api does not accept Objects as parameters. However, if I comment all the code of createMyAsset I get no errors, and I am also using an object in splitMyAsset and I have no problem there.
Can anyone explain me what this is happening? The problems I get happen when I try to instantiate the chaincode/run tests using npm test.
Thank you very much.
My service is designed in nodejs.
Below is my scenario
i have two controllers, one will be extending the other. there is a static function in both the controllers where in a static variable will be assigned some value.
depending on the condition of the data, im trying the make a call to the respective controller so that the static variable gets a appropriate assigned value.
Note:
The below code is just a snippet to explain the scenario and not the actual code of the application. But the order / calling / controller structure of this code snippet is exactly same. Also the listOfDept variable will be having separate business logic in the checkStart function of firstController and secondController.
// firstController.ts
firstController implements IFirstController {
private static listOfDept: string[];
static checkStart(){
firstController.listOfDept = // my logic to fill this object
}
constructor (){}
}
getRelevantData(next: (error: string, response: any) => void): void {
var myObject = firstController.listOfDept;
this.myRepository.uniqueData(myObject, next);
}
}
firstController.checkStart();
export = firstController;
//ifirstController.ts
interface IFirstController {
getRelevantData(next: (error: string, response: any) => void): void;
}
// secondController.ts
secondController extends firstController implements iSecondController {
private static listOfDept: string[];
static checkStart(){
firstController.listOfDept = ["Computer Science"];
}
constructor (){
super();
}
}
secondController.checkStart();
export = secondController;
//isecondController.ts
interface ISecondController implements ifirstController{}
//Controller calling the getRelevantData function
//middlewareController
middlewareController implements IMiddlewareController {
constructor(private firstController: IFirstController, private secondController: ISecondController) {
}
getDepData(data: any, next: (error: string, response: any) => void): void {
if(data.url = "fromParent") {
// im expecting this to make a call to checkStart() of firstController
this.firstController.getRelevantData();
} else {
// im expecting this to make a call to checkStart() of secondController
this.secondController.getRelevantData();
}
}
}
Problem faced with the above code
No matter which way the getRelevantData function is getting called, im always getting the value of listOfDept as computer science. It is never going in the checkStart function of first controller.
In general I would discourage using static methods for this kind of initialization and instead inject the required data into constructors or create factory methods for creating object with necessary data.
But, if you do want to use static properties, the problem is that you need to refer to the right parent class in the getRelevantData implementation. The class that constructed the instance can be accessed through constructor property. TypeScript does not process this scenario well, so you have to make a type cast:
// firstController.ts
class firstController implements IFirstController {
// Need to be `protected` to be accessible from subclass
protected static listOfDept: string[];
static checkStart(){
firstController.listOfDept; // my logic to fill this object
}
constructor (){}
getRelevantData(next: (error: string, response: any) => void): void {
// You need to refer to the constructor
let Class = this.constructor as typeof firstController;
var myObject = Class.listOfDept;
// the rest
}
}
firstController.checkStart();
//ifirstController.ts
interface IFirstController {
getRelevantData(next: (error: string, response: any) => void): void;
}
// secondController.ts
class secondController extends firstController implements ISecondController {
// No `listOfDept` definition here
static checkStart(){
secondController.listOfDept = ["Computer Science"];
}
constructor (){
super();
}
}
secondController.checkStart();
I would like to know if that's possible to cast a Dynamic to an other class (partially or totally)
For example, this code breaks :
class Test {
public function new() {}
public var id: String;
}
class Main {
public static function main() {
var x:Dynamic = JsonParser.parse("{\"id\":\"sdfkjsdflk\"}");
var t:Test = cast(x, Test);
}
}
with the following message
Class cast error
However, my "Test" class has an "id" field like the dynamic object. (That's an example, my use case is more complexe than that ^^)
So, I don't understand how to get an object from my Dynamic one.
This isn't exactly casting a dynamic to a class instance but may accomplish the same thing:
create an empty instance of the class with Type.createEmptyInstance
set all of the fields from the Dynamic object on the new class instance using Reflect
Example:
import haxe.Json;
class Test {
public function new() {}
public var id: String;
}
class Main {
public static function main() {
var x:Dynamic = Json.parse("{\"id\":\"sdfkjsdflk\"}");
var t:Test = Type.createEmptyInstance(Test);
for (field in Type.getInstanceFields(Test))
if (Reflect.hasField(x, field))
Reflect.setProperty(t, field, Reflect.getProperty(x, field));
trace(t.id);
}
}
You could use typedef
typedef Test = {
public var id: String;
}
class Main {
public static function main() {
var t:Test = JsonParser.parse("{\"id\":\"sdfkjsdflk\"}");
}
}
Json.parse returns anonymous structure(implementation platform dependent), typed as Dynamic. There isn't a single chance to cast it to anything but Dynamic, unless Json.parse returns Int, Float or String, which some parsers permit, but which isn't actually permitted by JSON specification.
That is this way because, the operation of casting doesn't check what fields some object have. Operation of casting only checks if the object is an instance of class you are casting to. Obviously, anonymous structure can't be an instance of any class(inside haxe abstractions at least).
However, the right way to perform the thing you seem to be trying to perform is the way stated by #Ben Morris, in his answer.