I've recently came across the following situation and looking for an advice / elegant solution of pretty tricky misbehave, IMHO.
Let's assume that I've created the following custom element definition in file bible-reference.js:
class BibleReference extends HTMLElement {
constructor() {
super();
}
get value() { return this._value; }
set value(newValue) { this._value = newValue; }
}
customElements.define('bible-reference', BibleReference);
We've defined our new element, and we have a value property getter/setter to interact with this element's value. Pay attention, that the getter/setter will be found on the element's prototype object, which is okay in itself, to be sure.
Now, let's review the following HTML layout that happened to be in my case:
<bible-reference id='elem-1'></bible-reference>
<script src="starter.js"></script>
<script src="bible-reference.js"></bible-reference>
The trick is in that starter.js content. If this script contains code below, things get broken:
let br = document.getElementById('elem-1');
br.value = 'some value';
The value is set on the non-upgraded yet element, therefore it is not the getter/setter that are being invoked, but just a new property is added to the element. Having this property set on the object itself, even after the upgrade is done the value's getter/setter are not accessible for this element instance anymore, since there is an (overriding) value property available before getting to the prototype.
I've solved it meanwhile by adding delete this.value; to the constructor, but... what a hack!
Any insights appreciated.
Another way to handle the issue would be to define the value property in the closure of the constructor().
<bible-reference id='elem-1'></bible-reference>
<script>
let br = document.getElementById('elem-1');
br.value = 'Genesis 1:1';
class BibleReference extends HTMLElement {
constructor() {
super()
var _value = this.value
Reflect.defineProperty(this, 'value', {
get: () => _value,
set: newValue => _value = newValue
})
}
}
customElements.define('bible-reference', BibleReference);
console.log(br.value)
</script>
But I guess you'll say it's a bad hack ;-) Anyway I'd prefer your delete solution.
Related
This might be a weird use case with no solution, but I would be happy to hear any answers on how to solve it.
Question:
I have a globally defined proxy:
this.appState = {}
global.globalState = new Proxy(this.appState, {
get: (target, prop, receiver) => {
console.log(this) // #3
return Reflect.get(...arguments)
},
set: (obj, prop, value) => {
console.log(this) // #2
obj[prop] = value
return true
}
})
And somewhere else I have a class:
export default class MyClass {
constructor() {
console.log(this) // #1
global.globalState["greet"] = "hi"
console.log(this.globalState["greet"])
}
}
Is it possible to bind this from the class constructor to be also this in getter and setter of the proxy? So basically, the context of the class constructor needs to be accessed from inside of the proxy getter and setter.
If your question is wether the line
global.globalState["greet"] = "hi"
can be somehow modified to change the this in the Proxies get method, then the answer is no. The arrow function is lexically bound (so its this can't change between different calls), and even if you would use a regular function instead there is still no way to call it by assigning a property, as the function is called by the runtime, and as Proxies are transparent, there is no way to influence that at all (you don't even know that the line calls a setter).
To dynamically change this you could use something like:
let context = null;
// inside the getter
(function thisInjection() {
}).call(context);
however that would require modifying the Proxies code, and in that case replacing this with context would be way easier.
Is there any point in repeating this pattern for every property in JavaScript?
class Thing {
get myProp() {
return this._myProp;
}
set myProp(value) {
this._myProp = value;
}
}
I understand that getters/setters can be useful if you're doing additional work in the methods, but recreating the basic functionality here seems like needless repetition. If I instantiate, I can still manipulate the backing property (._myProp) directly, so I feel like I could just as easily leave these out and perform my assignment and access in the more typical, ad-hoc fashion.
I suppose you could argue that defining the interface this way (with the underscore-prefixed property name) signals to users that it's not meant to manipulate the property, but that seems like a flimsy reason for potentially dozens of these.
In compiled languages, it's common for people to do this. This is because in those languages, assigning to a field and invoking a setter may be identical to the programmer, but they compile to two completely different operations.
If I wanted to add a side effect for setting a field in a C# class, for example, and that field was being set directly instead of through a setter? Changing it to a setter would cause some issues. I wouldn't have to rewrite any of the consuming code, but I would have to recompile all of it. This is, of course, a huge problem if your public releases are compiled.
JavaScript is subject to no such considerations, though, so making everything into a getter/setter prematurely is kind of silly. If you see someone doing this, more than likely you're dealing with someone who learned the convention from another language and carried it into JavaScript, without thinking a whole lot about why.
Using an accessor property in the fashion you describe (set and retrieve a "background" data property) is virtually semantically identical to using a data property directly. There are some differences: the accessor property will exist on instance's prototype, rather than on the instance directly (though the instance will have the "background" data property), but this won't really affect anything unless you are doing advanced introspection on your class instances.
The only advantage is ease of modifying the code if you want to introduce more sophisticated accessor behavior in the future. If you forsee a need to add accessor behavior, use this pattern to save yourself time in the future.
Property accessors are useful to provide side effects or change original behaviour:
class Thing {
get myProp() {
console.log('myProp was read');
return this._myProp;
}
set myProp(value) {
if (!value)
throw new Error('myProp cannot be falsy');
this._myProp = value;
}
}
There is no point in myProp getter/setter pure abstraction:
class Thing {
get myProp() {
return this._myProp;
}
set myProp(value) {
this._myProp = value;
}
}
If I instantiate, I can still manipulate the backing property
(._myProp) directly,
If private states are what you are looking for you can still use a weak map.
(function(scope) {
"use strict";
const prop = new WeakMap();
scope.Foo = class {
constructor() {
prop.set(this, {});
Object.seal(this);
}
get bar() {
return prop.get(this)._bar;
}
set bar(value) {
return prop.get(this)._bar = value;
}
}
}(this))
const f = new Foo;
f.bar = "bar";
f._bar = "_bar";
console.log(f.bar);
console.log(f._bar);
get and setters are also useful when implementing MVC, you can trigger events on property change.
(function(scope) {
"use strict";
const prop = new WeakMap();
scope.Foo = class {
constructor() {
prop.set(this, {});
prop.get(this)._target = new EventTarget
Object.seal(this);
}
get bar() {
return prop.get(this)._bar;
}
set bar(value) {
prop.get(this)._bar = value;
prop.get(this)._target.dispatchEvent(new CustomEvent('change', {
detail: value
}));
}
addEventListener(event, listener) {
prop.get(this)._target.addEventListener(event, listener)
}
}
}(this))
const f = new Foo;
f.addEventListener('change', function(event) {
console.log("changed!", event.detail);
});
f.bar = "bar";
This is beginners question..
I have been reading Angular2 documentation, I came across example in
Hierarchical Dependency Injectors chapter where Restore service is used to
make save/cancel functionality available for editing.
This is the service:
export class RestoreService<T> {
originalItem: T;
currentItem: T;
setItem (item: T) {
this.originalItem = item;
this.currentItem = this.clone(item);
}
getItem () :T {
return this.currentItem;
}
restoreItem () :T {
this.currentItem = this.originalItem;
return this.getItem();
}
clone (item: T) :T {
return JSON.parse(JSON.stringify(item));
}
}
I was so excited about it so I tried it myself!
First I'm setting the values like this:
ngAfterContentInit(){
this.metadata = {
languages: this.selected_languages,
countries: this.selected_countries,
international: false
}
}
set metadata(metadata: CvMetadata){
this._restoreService.setItem(metadata);
}
get metadata(): CvMetadata{
return this._restoreService.getItem();
}
After that I'm changing metadata properties values with ngModel
for example: [(ngModel)]="metadata.languages[0]"
The question: For my sruprise When I update the metadata property value with ngModel it works - the currentItem has changed and the orginalItem has not! What I do not understand is how is that possible? I thought that ngModel would use setter for setting the metadata properties. But the setter is called only once when I set the original data. How does the ngModel know that it should change only the currentItem not he originalItem?
Is this the black magic?
I know.. I only need someone to explain this, but unfortunately or fortunately there are only you guys!
Thanks!
RestoreService: it has two functions setter and getter;
setItem (item: T) sets originalItem, clones it and save the clone to currentItem.
getItem() just returns currentItem (the cloned item)
Now, ngModel gets the item metadata in order to get its property languages. So it will get currentItem. When ngModel sets a new property value, it doesn't need to set metadata(). it only need to set languages property inside metadata. So, it will get metadata again and sets the languages property.
Here is a plunker hopefully it will help
Another way of explaining it:
this.metadata = {language:['english']}; // you set metadata, effectively setting originalItem and cloning it to currentItem
this.metadata.languages = ['arabic']; // you get metadata "currentItem", set its property languages. Never actually setting metadata itself
Note on the clone function: the function clone in RestoreService is not cloning the object in a "practical" way. Because it uses JSON parser. See these question for more details and better cloning approaches: 122102, 728360
I am new to es6 and am wondering if this approach is OK.I do not want someone to set properties that I do not want on my object.
export default class User {
static getMembers() {
return ['username','email','fullname','birthdate'];
}
constructor(props) {
Object.keys(props).forEach((p)=>{
if (User.getMembers().includes(p)) this[p]=props[p]
});
Object.defineProperty(this,"username",{writable:false});
Object.defineProperty(this,"email",{writable:false});
}
any other approaches?
With your code as shown, you can't set any value for username or email. They'll always be undefined, as you've made them read-only and haven't assigned them a value. You also haven't done anything to prevent code with access to one of these objects setting a completely arbitrary property:
let u = new User();
x.anythingIWant = 42;
If you want to prevent code from adding properties to an object, you can do that with Object.seal (ref: MDN, spec), which prevents new properties being added and makes it impossible to re-define existing properties. So:
export default class User {
constructor() {
// ...create your desired properties
// Seal the object
Object.seal(this);
}
}
And if you also want to make username and email read-only, you'd also include the Object.defineProperty calls you have (before the Object.seal). Just make sure you assign them values first, as once you've done that defineProperty and Object.seal, you can't change them anymore.
I think I'd probably approach using that array of whitelisted properties slightly differently as well, by just looping through the array.
So:
export default class User {
static getMembers() {
return ['username','email','fullname','birthdate'];
}
constructor(props) {
// Create the properties
User.getMembers().forEach((name) => { this[name] = props[name]; });
// Make sure username and email have a value, because we won't
// be able to change it in a moment
// ...
// Make username and email read-only
Object.defineProperty(this, "username", {writeable: false});
Object.defineProperty(this, "email", {writeable: false});
// Seal the object
Object.seal(this);
}
}
In Typescript, how would I use a getter/setter for all the properties of an object? For example, I can have the following code:
class Div {
private _container: HTMLDivElement;
public get container() {
return this._container;
}
public set container(value) {
alert("Updated!");
this._container = value;
}
testDiv() {
this.container = <HTMLDivElement>document.createElement('div');
this.container.style.width = "100px";
}
}
var newDiv: Div = new Div();
newDiv.testDiv();
"Updated!" is alerted only once - when the div is first set (which, I guess, is really what it should be), but I want it to alert "Updated!" even when I'm setting a property on that object. Is there a way to do this, or should I come up with some kind of workaround?
Create an event system that triggers when your objects state changes. Do not allow direct access to properties. Funnel all activity through a controller that triggers state change as necessary.
Or look into using a framework that already does this. Backbone, Knockout, Angularjs etc.