40

The following code will throw an error only for the name property. It could be fixed by specifying name property as writable in Object.create arguments but I'm trying to understand why is this happening(and maybe there is a more elegant way to fix it).

var BaseClass = function (data) {
  Object.assign(this, data);
}

var ExtendedClass = function () {
  BaseClass.apply(this, arguments);
}

ExtendedClass.prototype = Object.create(BaseClass);

console.log(new ExtendedClass({ type: 'foo' }));
new ExtendedClass({ name: 'foo' });
G07cha
  • 4,009
  • 2
  • 22
  • 39
  • 6
    Because all functions have a native `name` property that returns the function's name. But, as is the case with JavaScript, inherited properties can be overwritten by creating new ones. – Scott Marcus May 31 '17 at 15:00
  • @ScottMarcus, thanks for the answer, that was obvious it's a shame that I didn't think about that. Is there a more robust way to bypass that error in class inheritance? – G07cha May 31 '17 at 15:04

9 Answers9

51

If you get this error in Angular+Typescript+NgRX:

You can use the spread operator to take a shallow copy of a readonly object to make it readable, however you may not want this depending on your situation.

let x = [...y];

If you're using Redux / NgRX, there's a chance your selector could be returning a readonly object with a reference to the store, which can throw exceptions when trying to alter that object property via template binding. Depending on your situation, you can take a deep copy to remove the store reference.

let x = JSON.parse(JSON.stringify(y));
user12180028
  • 511
  • 1
  • 4
  • 2
  • 2
    I am getting this error in an angular+ngrx error, but what I do not understand why I get it for every object, even for objects not in the state or reducers. – albanx Feb 27 '21 at 23:02
  • 1
    This is so clever, thanks for saving my day – Viet Nguyen Jul 20 '22 at 12:27
24

You cannot modify the name property of a function. The descriptor says it is not writable...

var BaseClass = function (data) {
  Object.assign(this, data);
};

console.log(Object.getOwnPropertyDescriptor(BaseClass, 'name'));

But since it is configurable, you could use Object.defineProperty().

var BaseClass = function (data) {
  Object.assign(this, data);
};

Object.defineProperty(BaseClass, 'name', {
  writable: true,
  value: 'Foo'
});

console.log(BaseClass.name);

EDIT

I'm back! So... As I said previously in comments, I think I have identified your problem. I answered a bit too fast and did not see that your ES5 inheritance is wrong.

ExtendedClass.prototype = Object.create(BaseClass); is not what you want to do. Doing so means the prototype of ExtendedClass becomes a constructor function. This obviously generates an unexpected behavior.

function BaseClass(data) {
  console.log(this instanceof BaseClass); // "this" is not an instance of "BaseClass"
  console.log(this instanceof Function); // "this" is a function
  console.log(this.name); // "this" is "BaseClass"
  
  Object.assign(this, data);
}

function ExtendedClass() {
  BaseClass.apply(this, arguments);
}
ExtendedClass.prototype = Object.create(BaseClass);

new ExtendedClass({ type: 'foo' });

In your code, this is a function and refers to BaseClass. That is why you are not allowed to modify its name...

In fact, when working with inheritance in JavaScript, you generally need these two lines:

ExtendedClass.prototype = Object.create(BaseClass.prototype);
ExtendedClass.prototype.constructor = ExtendedClass;

Here is a valid implementation:

function BaseClass(data) {
  console.log(this instanceof BaseClass); // "this" is an instance of "BaseClass"
  console.log(this instanceof Function); // "this" is not a function
  console.log(this.name); // "this" has no name yet
  
  Object.assign(this, data);
}

function ExtendedClass() {
  BaseClass.apply(this, arguments);
}
ExtendedClass.prototype = Object.create(BaseClass.prototype);
ExtendedClass.prototype.constructor = ExtendedClass;

var instance = new ExtendedClass({ name: 'foo' });

console.log(instance.name); // foo
console.log(BaseClass.name); // BaseClass
console.log(ExtendedClass.name); // ExtendedClass
Badacadabra
  • 8,043
  • 7
  • 28
  • 49
  • Thanks for the detailed answer, I've actually had mentioned writable property modification in my question. But besides of that, I'm trying to find a more elegant way to handle it if there is any. – G07cha May 31 '17 at 15:15
  • The best solution is probably to avoid the "name" keyword, just like you would avoid using reserved words like `var`, `typeof` or `arguments`... ;) – Badacadabra May 31 '17 at 15:22
  • You're right, but `name` is pretty general property for classes and as in my case I'm creating a class for a backend response which contains `name` property and therefore, changing it will cause confusion for other developers. – G07cha May 31 '17 at 15:31
  • I think I have just understood your question properly. I'll probably modify my answer to give you more insights... :) – Badacadabra May 31 '17 at 15:36
  • @KonstantinAzizov: I have just modified my answer. Hope this helps... ;) – Badacadabra May 31 '17 at 17:33
  • many thanks for your efforts, that is definitely more elegant than overriding the name property with `Object.defineProperty`. I've seen your's other answers, you are awesome guy, keep it up! – G07cha May 31 '17 at 18:45
  • 2
    Wow! Thank you VERY MUCH for your upvotes, dude. I appreciate a lot. In fact, Stack Overflow is not always fair... You generally get more reputation with quick and short answers, but I often prefer to be slow and post long answers when the question is interesting. To be honest, this is not a lucrative business in terms of reputation points. I have posted some in-depth answers which received nothing. But at least, if it can help someone in the future, this is great. Happy JavaScripting! :) – Badacadabra May 31 '17 at 19:06
5

The name is reserved property of Function object to which you are trying to set it in. You cannot set it.

documentation for name property is at MDN.

Misaz
  • 3,694
  • 2
  • 26
  • 42
  • But it works well with [ES6 classes](http://jsbin.com/quwivisiwi/1/edit?js,console), is there any way to make constructor functions to act like a class constructor in ES6? – G07cha May 31 '17 at 15:10
5

Used ES7+ or TypeScript spread operator feature to overcome this

obj = { ...obj, name: { first: 'hey', last: 'there'} }
Manas Sahu
  • 779
  • 8
  • 8
3

If you get this error in Angular+TypeScript:

WRONG / INVALID:

@Output whatever_var = new EventEmitter();

GOOD / CORRECT:

@Output() whatever_var = new EventEmitter();

KANJICODER
  • 3,611
  • 30
  • 17
3

Most likely you're using readonly object that can't be edited. Use cloneDeep from lodash.

const x = cloneDeep(y);

where y is readonly object & use x instead of y in your code.

Vikram Sapate
  • 1,087
  • 1
  • 11
  • 16
0

I ran into this issue in Angular, while setting a local variable from ActivatedRoute's queryParams, and attempting to conditionally either override or merge... Duplicating beforehand did the trick:

updateQp(qp = {}, force = false) { 
    let qpLoc = Object.assign({}, this.queryParamsLocal)
    this.queryParamsLocal = force ? qp : Object.assign(qpLoc, qp)
}
corg
  • 428
  • 4
  • 14
0

In my case, I was trying to swop 2 elements in an array in redux. Here's a simple way of how I altered the redux state while avoiding the readonly issue:

let newData = []
let data = store.getState().events.value

for(let i = 0; i < data.length; i++) {
  if(i === fromIndex) {
    newData.push(data[toIndex])
  }else if(i === toIndex) {
    newData.push(data[fromIndex])
  }else {
    newData.push(data[i])
  }
}
this.setEventsData(newData)

The above code creates a new empty array. It then iterates through the readonly array and pushes each item into the new array. This effectively creates a new array with the items of the readonly array positioned in a different order. If you are having trouble editing the values, you could alter the above code and create a new object with the altered values and insert that into the position where you'd like to make the change.

0

I bumped into this error once with Nestjs, and I fixed it by turning the property type to 'any'

type Product = {
  readonly owner: string;
};

const obj: Product = {
  owner: 'John',
};

(obj.owner as any) = 'James';
Anis
  • 77
  • 2
  • 7