Assigning a value to a property without a setter

Like @laszloabonyi42094240, I added an updateInsurance method to a new Doctor subclass. I wanted to try to assign a value to the _insurance property by polling a getter get insurance, instead of using a setter. We polled getters like this in the Team Stats project, to access the arrays in _players and _games from the addPlayer and addGame methods, in order to add a new player/game using .push(). The only difference being that in Team Stats we were adding objects to arrays using .push(), whereas this time I was trying to completely reassign the _insurance property by using a reassignment operator (=).

I thought it would work, but it didnā€™t, and I donā€™t understand why. Here are the relevant parts of my code:

class HospitalEmployee {
  constructor(name) {
    this._name = name;
// etc.
}

class Doctor extends HospitalEmployee {
  constructor(name) {
    super(name);
    this._insurance = null;
  }
  
  get insurance() {
    return this._insurance;
  }
  
  updateInsurance(level) {
    this.insurance = level; //works if change to: this._insurance = level;
  }                         //i.e. if add underscore   ^

const doctorCalafell = new Doctor('Calafell');
doctorCalafell.updateInsurance('Level 1');
console.log(doctorCalafell.insurance);

The error logged is:

TypeError: Cannot set property insurance of #<Doctor>
which has only a getter

It only works if I:

  1. access _insurance directly (as highlighted in the comment in the code above), which I obviously donā€™t want to do as itā€™s been marked as private;
  2. use a setter instead of the method updateInsurance(), as follows:
set insurance(level) {
  this._insurance = level;     //this works
  }

doctorCalafell.insurance = 'Level 1';
console.log(doctorCalafell.insurance);   //correctly prints "Level 1"

At first, I thought it might have something to do with using null as the value initially assigned to _insurance, but it also doesnā€™t work if you change this to undefined, leave it empty (this._insurance;) or assign it a string. I decided to use null as I wanted to start with this field empty, and I thought null would be appropriate to indicate that the value was pending. Is this an appropriate use of null, or would undefined or leaving the property empty (this._insurance;) be better here?


Is this why?

https://discuss.codecademy.com/t/team-stats-project-why-does-the-solution-add-the-data-to-the-getter-method/414366/11?u=jon_morris

So is it that getters can give other methods access to the private values but NOT write permission? Iā€™m not entirely sure what write permission is, but Iā€™d take a guess that it means permission to change or reassign a valueā€¦? So would that mean that pushing a value into an array isnā€™t writing because the array value itself remains unchanged (only its contents change)?..whereas to reassign the ā€œwholeā€ value of _insurance would require write permission, which only a setter can giveā€¦ is that it?

5 Likes

In JS write permission is universal. We have protected variables by proxy, not by de facto.

OKā€¦ so thatā€™s not the reason my method .updateInsurance() canā€™t use the getter get insurance to access the private property _insurance, and then reassign its valueā€¦

At least I attempted to find a reason :wink:

Looking forward to finding out what the actual reason isā€¦ at the moment Iā€™m still at a loss as to why such a method can ā€œpushā€ to an array having accessed it via the getter, but not reassign a propertyā€™s value with the assignment operator (=) having accessed it in the same way.
:grimacing:

Weā€™ve been over this already.

obj = {
    _prop: [],
    get prop () {
        return this._prop;
    },
    set prop (value) {
        this._prop.push(value);
    }
}
obj.prop = "new value"
console.log(obj.prop)
// ['new value']

This is what has everyoneā€™s back up that is against JS setters.

3 Likes

Sorry, maybe I didnā€™t explain my actual question very clearlyā€¦

I totally understand why the setter works in your example above, and also why using it in this way, to push a new value into a privately marked array, is controversial for some people.

In my example above, instead of adding a new value to an array, Iā€™m actually reassigning the whole value (i.e. changing this._insurance: null to this._insurance: Level 1). As I expected, it works using a setter (obviously changing the statement from this._prop.push(value) to this._prop = value.

However, instead of using a setter, what I wanted to do was practise using the getter to access the private property (as we have been discussing here) and use a method (updateInsurance) to reassign the value. But it didnā€™t work, as Iā€™ve described above, and I donā€™t understand why, especially after what Iā€™ve understood by this.

Sorry, if Iā€™m missing something obviousā€¦

6 Likes

Iā€™ve just tried to do the same thing, but with a more simplistic version, without the added complication of parent and subclasses: and it still doesnā€™t workā€¦

Works when pushing to an array:

const obj = {
  _insurance: [],
  get insurance() {
    return this._insurance
  },
  updateInsurance(level) {
    this.insurance.push(level)     // uses getter to access _insurance
  }
}

console.log(obj.insurance)       // prints "[]"   (as expected)
obj.updateInsurance('Level 1')   // calls method to add "Level 1" to array
console.log(obj.insurance)       // prints "['Level 1']"   (as expected)

Doesnā€™t work when reassigning entire value:

const obj = {
  _insurance: null,
  get insurance() {
    return this._insurance
  },
  updateInsurance(level) {
    this.insurance = level      // uses getter to access _insurance
  }
}

console.log(obj.insurance)      // prints "null"   (as expected)
obj.updateInsurance('Level 1')  // calls method to assign "Level 1" to _insurance
console.log(obj.insurance)      // still prints "null"   (NOT as expected)
// I expected the getter to give the method access to _insurance and
// therefore enable it to reassign the value from "null" to "Level 1"...???
6 Likes

Youā€™ve discovered another nuance that maybe we havenā€™t touched on yet. There is a difference in values from one example the otherā€¦ Oneā€™s a data structure and the other a single value. It may prove out that we cannot set a single value without a setter.

The binding to a list may be what allows other methods to append the array in a private attribute. Will have to play around and see what happens.

However, before we draw any conclusions be sure the syntax is complete. It looks like you have a missing brace in the second example (to close the method).

Updateā€¦ Nope. Still null.

Yup, it needs a backing variable to work. May as well add a setter for single assignments.

obj = {
  _insurance: null,
  get insurance() {
    return this._insurance
  },
  set insurance(level) {
    this._insurance = level;
  }
}
5 Likes

Thanks for looking at this again :+1:
Itā€™s good to have some expert confirmation!
Thanks for also spotting the missing curly braces (Iā€™d missed them in both examples)ā€¦ they were in my original code but got lost. Iā€™ve updated my examples just in case someone else thinks that may be the reasonā€¦

Just one thingā€¦
I get that youā€™re confirming we really need a setter for single assignments (otherwise weā€™d have to access the private _property directly (which is exactly what we are trying to avoid)ā€¦
butā€¦ what do you mean by ā€œbacking variableā€?

I guess this is a good argument to support those in favour of setters! :smiley:

2 Likes

It does bolster the argument for using them for assignment, and not using them for pushing.

this._prop

Basically, it is the private attribute, where obj.prop is the public alias that invokes the getter.

4 Likes

This nuance makes me ponder whether there is something analogous to the difference between a const and a let or var, in terms of using those to store objects and arrays.

If we store objects and arrays in a let or var, then we can re-assign the array or object whole. But if we store them in a const, we canā€™t; but we can still fully manipulate their array elements and object key:value pairs.

What is ā€˜protectedā€™ by the const is re-assigning to the const itself, but it doesnā€™t prevent us from manipulating an object (data structure) stored inside one.


Is this whatā€™s going on here?
So in essenceā€¦ it seems like the const's reference is ā€˜write-protectedā€™.

And in the object.property scenario, with only a getter but no setter, then so too it seems the property's reference is ā€˜write-protectedā€™ (directly contravening _convention aside).

The presence of the getter similarly allows us to manipulate the data inside the referenced data structure, but not to change the reference itself.

For that we need a setter.


This was a useful analogy in my own headā€¦ so thought I would share.

p.s. Roy can perform mild janitorial duties / full open-heart surgery on my terminology as deemed necessary :smile:

8 Likes

An array or object are values that reference other values. While we cannot alter its value (if set as a constant), we can still alter the values it references. const is permanent for the session.

Tip: When working in the JS console, donā€™t use const or let or you have restart the session and lose all your work if you need to correct something. When you get the bugs ironed out or theory proved out, then in the saved code add in the appropriate keywords as apply.

Properties are in no way similar to constants, since they are not actually ā€˜write protectedā€™ if we know how to access them. Objects are mutable to the point where we can even remove attributes.

4 Likes

For clarification:

I do state at that outset that I found these two scenarios to be analogous; Iā€™m not making any claim here of a direct technical equivalence.

If we are going to be good developers and good troubleshooters, then we need all our problem-solving skills at the ready; and not restrict ourselves to swallowing technical terminology by rote.

For sub-clarification:
Particularly as a beginner, understanding the technical definitions is important. However, ultimately a real depth of understanding will come from your own thinking as you gain more knowledge and are able to relate concepts to other concepts which you already grasp.

Or: how the brain works.

If you donā€™t trust in it to make its own leaps, you wonā€™t make a developer; once the syntax falls into the background, itā€™s you and your brain.

1 Like

Not sure what analogy you wish to draw, here. Variable declaration keywords and object properties/methods are worlds apart.

1 Like

Scenario 1
Const: Cannot be re-assigned. But stored objects and arrays remain mutable.

Scenario 2
Object._property with a getter but no setter: Adhering to convention, the property cannot be re-assigned. But stored objects and arrays remain mutable.


Therein lies the similarity: an analogous situation. The upshot is the same, but Iā€™m not making a claim of technical equivalence between the two language features.

And so this is a matter of reasoning :thinking:, not one of technical correctness :nerd_face:

3 Likes

As a directive to other developers, the property should not be re-assigned. There is no write protection and nothing analogous with const.

When a developer sees a property written as a backing variable, they know to look for and refer to, at the very least a getter. If the value is a data structure then expect to see addSomething methods, or a setter method that pushes to or updates the data structure, and even weirder syntax at the caller end.

this.appetizers = dish;

This is all to protect the data as much as we can, which is to say, as much as we can. Short of a database, that is the best we can do; we hint to other programmers to acknowledge this need for protection and hope they will follow a regimen that wonā€™t encroach.

3 Likes

The full quote was:

Appreciate we are on a FAQ page, but I was engaging in intellectually honest discussion, and the above chopping up of a quote in order to provide correction is not in keeping.

The statement is obviously true. By choice and not by built-in restriction, but a truthy statement nonetheless.


Analogies

Are a mental tool for transferring meaning (not exacting likeness, which wouldnā€™t be analogy) from one concept or subject to another. Yes you can even do this with whole different subjects!

Letā€™s see what wikipedia has got to say about the analogy:

It has been argued that analogy is ā€œthe core of cognitionā€.

A powerful tool for learning, then.

So if an analogy is helpful to someone to take on board a new concept, then that strengthens their understanding. Thatā€™s down to the individual learner and for nobody else to impose upon either way.

One can agree or disagree with someoneā€™s reasoning; but thatā€™s just opinion.

8 Likes

When you write return this._insurance it means than you get the value, but not the variable or property.
Look, you do something like this:

let myFavFruit = 'banana';
let yesterdayFruit = return myFavFruit;
// or let yesterdayFruit = "banana", where "banana" is the Value of myFavFruit;

And after this you try to do this "banana" = "new fruit" instead of myFavFruit = "new fruit".

Thatā€™s why your code works when you write this._insurance = level.

4 Likes

That makes a whole lot of sense, and explains whyā€¦

ā€¦when the value returned fromā€¦

get insurance() {
  return this._insurance
}

ā€¦ is an array, we can add an element to it using this.insurance.push("additionalInsurance"), and thereby update the array assigned to the protected property _insurance without having to use a setter or access it directly.

ā€¦ but, why we canā€™t use this same value to reassign the whole property _insurance, as we need to access the whole property to do that (not just the value) e.g.

set insurance(newInsurance) {
  this._insurance = newInsurance
}

Or as you sayā€¦

I get the analogy you are trying draw with your fruit example with variables (rather than object properties), but as the code itself isnā€™t actually valid, Iā€™ve come up with this insteadā€¦ what do you think (as a workable extension of your analogy)?

// This works
let myFavFruits = ["bananas", "apples"]
const returnMyFavFruits = () => myFavFruits  // returns the array (value)
returnMyFavFruits().push("pears")            // adds "pears" to the array
console.log(myFavFruits)         // prints ["bananas", "apples", "pears"]

// This doesn't work
let myFavFruit = "banana"
const returnMyFavFruit = () => myFavFruit   // returns the string (value)
returnMyFavFruit() = "pear"  // Error - equivalent to:  "banana" = "pear" 
console.log(myFavFruit) 
// line 3 causes the error because you can't assign a value to a value!!
// to work and print "pear", line 3 has to be:   myFavFruit = "pear"
2 Likes

This is related to ā€œpass by valueā€ or ā€œpass by pointerā€.

when you call getSomething, you get the pointer to a variable, but not the variable itself.
So when you do
let temp = getSomething
temp = anotherThing

You are simply reassigning the pointer to anotherThing, but not the variable that the pointer points to.

Here you try to do two different operations: ā€˜add an element to the arrayā€™ and ā€˜reassign the myFavFruits variableā€™.

You understood :slight_smile: If we want to reassign the myFavFruits we need use myFavFruits = newValue, where newValue is what we put into the varable. If you add the value you update the myFavFruits which is an array. So you can do this two ways:

myFavFruits.push(value); // just the same array.push()
returnMyFavFruits().push(value); // just the same array.push()

Ah, and it will be better to use const for an array saving memory and having the way to update your array (add elements, delete elements, change elements etc.). But this way you canā€™t reassign the variable holding this array.

2 Likes