I am passing a class method as a parameter to a new class instantiation like this:
class Abc {
constructor() {
this.a = () => { };
}
b = new Def(this.a);
}
I get 'cannot read property a of undefined' in browser console. Why is a undefined inside b = new Def(this.a)? On debugging, I found that browser throws the error and the constructor code is never reached. Why is this happening?
Note: I am using babel, so I can use class fields and hence b = new Def() is a valid syntax here.
That's how class fields work, they are evaluated before constructor body (but after super()). Line 1 is evaluated before line 2, and the order in which constructor and b field are ordered doesn't matter:
constructor() {
this.a = () => { }; // 2
}
b = new Def(this.a); // 1
Since class fields are already in use, in order to maintain proper execution order it should be:
a = () => { }; // 1
b = new Def(this.a); // 2
constructor() {}
Related
I'm using eslint and it states that using 'Function' as a type is unsafe.
Is there a better way of doing this, so that I don't use the function type:
I have this confirmation dialog that appears when I'm trying to delete something:
export class DialogConfirmationComponent {
message: string = "";
txtBtnConfirmation: string = "Confirm";
actionConfirm: Function = () => { };
actionCancel: Function = () => { };
}
And when I click on the confirm button on the html this actionConfirm Function is called.
I want it to be generic so that I can call this dialog for different components and uses, and define the action using bind on the component, like this:
export class ItemCardComponent {
constructor(
private dialog: MatDialog
) { }
public openDeleteDialog(item: Item): void {
const dialogRef = this.dialog.open(DialogConfirmationComponent, {
width: '500px',
panelClass: 'no-padding-dialog',
autoFocus: false
});
dialogRef.componentInstance.message= "Are you sure?";
dialogRef.componentInstance.txtBtnConfirmation = "Delete";
dialogRef.componentInstance.actionConfirm = this.delete.bind(this, item);
}
private delete(item: Item): void {
// ToDo: delete
}
}
So far it's working, but is there a better way of doing it without using the Function type?
You could use this if the function is very general and you are unsure of the number and type of parameters or the nature of the return type:
(...args: any[]) => any
However, if you are aware of the return type, you may use this in its place. Note that the return type in this case is void:
(...args: any[]) => void
If you want to boost type safety, you may also replace any with types. In this example, you are presuming that the parameters are of the type number or string and that the return type is unknown.
(...args: (string & number)[]) => unknown
Please take into account that this question is about Typescript and not vanilla Javascript.
I am trying to deserialize a very simple JSON string into a Typescript object and then casting into the correct type.
After casting at const obj = <FooClass>JSON.parse(s) I would expect the typeof operator to return FooClass. Why the operator still returns object ?
Why does casting here fails? How can I deserialize and still have access to somefunc ?
Example code:
class FooClass {
public baz = 0
public somefunc() {
return this.baz * 2
}
}
const jsonData = {
baz: 1234,
}
test('deserialize example', () => {
const s = JSON.stringify(jsonData)
const obj = <FooClass>JSON.parse(s) // Cast here
console.log(typeof obj) // typeof still returns object
console.log(obj)
console.log(obj.somefunc())
})
Output:
console.log
object
at Object.<anonymous> (tests/deserialize.test.ts:15:11)
console.log
{ baz: 1234 }
at Object.<anonymous> (tests/deserialize.test.ts:17:11)
TypeError: obj.somefunc is not a function
In typescript you can cast any (return type of JSON.parse) to anything. The responsibility of ensuring if the casting is "correct", and the casted value indeed matches the type it's being casted to is yours.
Casting is only telling the type checker how to treat that value from the point of casting.
Turning that object to an instance of your class is also your responsibility. You could however do something like this:
type Foo = {
baz: number
}
class FooClass {
public baz: number = 0
constructor(input: Foo) {
this.baz = input.baz
}
public somefunc() {
return this.baz * 2
}
}
const rawFoo = JSON.parse(s) as Foo
const fooClassInstance = new FooClass(rawFoo)
// ready to be used as an instance of FooClass
Playground
For completion, I copy here a solution that I find both efficient and clean:
test('casting fails example', () => {
const s = JSON.stringify(jsonData)
const obj = Object.assign(new FooClass(), JSON.parse(s))
console.log(typeof obj)
console.log(obj)
console.log(obj.somefunc())
})
This could later be improved using generics
function deserializeJSON<T>(c: { new (): T }, s: string): T {
return Object.assign(new c(), JSON.parse(s))
}
const obj = deserializeJSON(FooClass, s)
How can I use map to return this output
get funcone() { return this.Form.get(funcone"); }
get functwo() { return this.Form.get("functwo"); }
get functhree() { return this.Form.get("functhree"); }
get funcfour() { return this.Form.get("funcfour"); }
I used this array
FormValues=['funcone','functwo','functhree','funcfour'];
And this map
FormValues.map(Value=>{
get Value() { return this.Form.get(Value); }
})
I would appreciate any help!
Presumably you want to define those functions as getters on some object, or class. Let's assume it's a class.
That syntax can't work - it creates a Value getter, rather than funcone getter. Now, while you can define a getter using a variable for the getter's name:
let propName = "foo";
class Foo {
get [propName]() { return "You're getting a foo"; }
}
new Foo().foo
// => "You're getting a foo"
as far as I know, there's no way to make a loop inside the class declaration, nor a way to keep reopening a class and adding new stuff to it like in Ruby, so class definition inside a loop also won't work.
However, class syntax is just a sugar for the older prototypal inheritance, so everything we can do with the class syntax, we can also do without it (though vice versa does not hold). In order to add new stuff to the class, we just need to stick it to the class's prototype object. We can explicitly define a getter method using Object.defineProperty.
class Foo {
constructor() {
this.Form = {
one: 1,
two: 2,
three: 3,
four: 4
}
}
}
let props = ['one', 'two', 'three', 'four'];
props.forEach(propName =>
Object.defineProperty(Foo.prototype, propName, {
get: function() { return this.Form[propName]; }
})
);
new Foo().three
// => 3
It would be almost the same code to give the getters to an object rather than a class; you'd just be defining properties on the object itself, rather than on a prototype.
I'm having an issue with calling my data from a json file. When I click a button to have it appear in a textarea, it does nothing for the first click, but works like expected after that. What the program does is gets an id from dashboard and based on that id grabs different json file to pull in.
The program shows an error of:
ERROR TypeError: Cannot read property 'data' of undefined
at StepService.webpackJsonp.102.StepService.populateList (step.service.ts:69)
at CalibrationDetailComponent.webpackJsonp.101.CalibrationDetailComponent.next
step.service.ts
private jsonData: any; //Json data
public list: String[] = []; //Holds the list of steps
public listLength: number; //Length of the list
public listCurrent: number = 0; //Current step the list is on
//Gets the json file
public getJson(): Observable<any> {
return this.http.get(this.jsonUrl)
.map(response => response.json());
}
//Subscribe
public subScribe2Json() {
this.getJson().subscribe(data => (this.jsonData = data));
}
//Populates the list from the json so I can pull out specific steps
public populateList() {
this.jsonData.data.forEach(element => { //The line that throws the error
this.list.push(element.name);
});
this.listLength = this.list.length;
}
//Returns the mainStepText with the current step
public getJsonData(): String {
this.mainStepText = this.list[this.listCurrent];
return this.mainStepText;
}
calibration-detail.component.ts
next button method
next() { //Advances step
this.stepService.subScribe2Json();
if (this.stepService.listCurrent < 1) { //Makes sure only runs once to populate the list
this.stepService.populateList(); //Populates list from the json array
}
if (this.stepService.listCurrent < this.stepService.listLength) { //make sure dont go past number of steps
this.stepService.subScribe2Json(); //Sub to list
this.mainStepText = this.stepService.getJsonData(); //Grab the data from the list and output to main textarea
this.stepService.listCurrent ++; //Increments the step
This is not a complete solution but an answer to what the problem is. And direct your thought into the right direction depending on what you want to achieve.
You call
this.stepService.subScribe2Json();
if (this.stepService.listCurrent < 1) {
...
this calls the first method and immediately the second without waiting for the data. And then of course it fails because it is not there yet.
Depending on your use case you could either return the Observable (maybe change it to a Promise,... not 100% sure) and then:
return this.getJson().subscribe(data => (this.jsonData = data));
and something like
this.stepService.subScribe2Json().then(/* do all stuff here */);
or initialize
private jsonData: any = [];
but here of course you don't have anything on the first run.
Let's say I have a function:
angular.forEach(myElements, function prepareElements(myEl: HTMLElement, index) {
myEl.dataset.myProperty = "whatever";
})
The problem I get is
error TS2094: The property 'myProperty' does not exist on value of type 'DOMStringMap'
I don't really understand the interface in lib.d.ts
interface DOMStringMap {
}
declare var DOMStringMap: {
prototype: DOMStringMap;
new (): DOMStringMap;
}
then later on...
interface HTMLElement {
dataset: DOMStringMap;
hidden: boolean;
msGetInputContext(): MSInputMethodContext;
}
Is it just me or is this a little unclear?
I tried casting it <DOMStringMap>myEl.dataset.myProperty = "whatever", which did nothing...
The DOMStringMap interface is empty because the spec is not finalized : https://developer.mozilla.org/en/docs/Web/API/DOMStringMap
In the meantime you can simply use: myEl.dataset['myProperty'] = "whatever";
you can also cast it to user defined type
type IType = {myProperty: string}
....
(myEl.dataset) as IType.myProperty = "whatever";