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
Related
I'm a fan of Vue which a try to use on some occasions. Anyway, there is something I always found not so handy with it: reactivity lies within $data. Well not always, as external data can be tracked by Vue, as in computed properties, in templates… But I found this way uncomfortable and not always consistent (see another question about it, here Reactivity on Variables Not Associated With Data, Computed, etc). So my decision now is use $data as the main source of reactivity and stop trying to find other ways.
However, reactivity within $data poses me a problem in what is a common case for me: many pieces of data here and there in other imported objects. This makes even more sense as I consider Vue as the View end not the business logic. Those imported objects are sometimes complex and within Vue components, I found no way to cherry pick pieces of information and kind of ask Vue to bind to them. The only way was to declare entire objects in the $data section which makes tracking very heavy: loads of setters/getters when only one would be enough in a simple component, for example.
So I designed a class called 'Reactor' whose instances role is to install getter/setters on any piece data of my wish in a complex object (or more than one). Those instances are imported into Vue components and then $watchers of Reactor instances have properties which can contain as many functions as I wish which are called when pieces of data are altered through the Reactor. To make things simple by default is filled with the same property name as the data it bounds to. This precisely those function which will update $data when external data change.
class Reactor {
constructor() {
this.$watchers = {};
}
addProperty(originalObject, keyString, aliasKeyString) {
if(aliasKeyString === undefined) {
aliasKeyString = keyString;
}
if(this[aliasKeyString] !== undefined || originalObject[keyString] === undefined) {
const errorMessage = `Reactor: cannot add property '${aliasKeyString}'!`;
console.error(errorMessage);
throw errorMessage;
}
this.$watchers[aliasKeyString] = [];
Object.defineProperty(this, aliasKeyString, {
set(newValue) {
const oldValue = originalObject[keyString];
originalObject[keyString] = newValue;
this.$watchers[aliasKeyString].forEach((f) => {
if(typeof f === "function") {
f(newValue, oldValue, aliasKeyString);
}
});
},
get() {
return originalObject[keyString];
},
});
}
}
An example can be seen in the codepen here: https://codepen.io/Djee/pen/gyVZMG
So it's sort of an 'inverted' Vue which allows updating $data on external conditions.
This pattern also helped me resolve a case which was rather difficult before: have a double-bind on an input with a filter in-between which will set the input and its attached external value straight upon #change event only. This can be seen in the same codepen given above.
I was a little surprised to have found nothing taking this in charge in Vue itself. Did I miss something obvious? This is mainly the purpose of this somewhat long introduction. I had no time to check whether Vuex would solve this nicely.
Thanks for any comments as well.
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";
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.
I'm having trouble updating an array that is displayed as a list. I'm trying to make Vue detect the changes using $set() (as explained in the documentation), but I can't make it work.
Here's my code:
this.choices = this.currentScene.choices;
for (i = 0; i < this.choices.length; i++) {
choice = this.currentScene.choices[i];
choice.parsedText = this.parseText(choice.text);
this.choices.$set(i, choice);
}
Vue still doesn't update the view. What am I doing wrong? Thanks in advance!
Edit: Yes, "this" refers to the Vue instance.
It would definitely be useful to have a JSfiddle of your code, but I'm going to take a crack anyways.
I'm not sure you need to use that function to update the array, since as the documentation points out, its only when you need to change the index of the item.
JavaScript has a built in function called .map that takes a callback function and returns a new array with the callback applied to each item.
For example, you could translate your function to this, assuming that .parseText is a method on the Vue class.
var self = this; // so that we can access the Vue class inside map
this.choices = this.currentScene.choices.map(function(choice) {
choice.parsedText = self.parseText(choice.text);
return choice;
});
And Vue should pick up those changes.
You could use a computed property for this, so you never have to manually update the array. Anytime choices changes you would see the change reflected in this.parsedChoices:
computed: {
parsedChoices: function(){
return this.currentScene.choices.map(function(choice) {
choice.parsedText = this.parseText(choice.text);
return choice;
}.bind(this)); // bind Vue class as value of `this` inside func
}
}
I'm trying to explore using ES6 classes instead of how we do it currently, using the Function.prototype means. Currently our API looks like:
var myclass = createClass('MyClass', {
test : function() {}
});
We iterate through the object and apply those properties onto the Function that we return, basically a prettier way than to do so that it's more inline with other programming languages of sorts:
function MyClass() {}
MyClass.prototype.test = function() {};
We also cache the class onto an object where name is the key and the function is the value for use throughout our application. The class name can be namespaced so you can have My.Cls and it will split by the period and then cache it onto the manager but it also can be retrieved via window.My.Cls.
Looking into ES6 classes, I don't see how I can keep the createClass function. Would love something like:
function createClass(name, config) {
return class name config;
}
I didn't expect it to work and it doesn't.
Two issues I have here:
How can I create a class using a variable as the class name?
How can I create a class and assign the properties via the config object argument?
Not sure this would be possible. We don't plan on keeping the createClass, we hope to keep it for now and upgrade our legacy "classes". I'd like to start using ES6 classes but not break the whole app for however long it'll take us to fully upgrade.
The only good upgrade route is to refactor the property hashes into proper classes. You can start that work and keep using your hash-based classes in the meantime, which will lighten the requirement to do it all at once.
If you have a limited number of "class" name:config pairs -- which you should for maintainability reasons -- then you can replace createClass with an implementation that does:
class Foo { ... }
class Bar { ... }
let classes = {'Foo': Foo, 'Bar': Bar};
function createClass(name, config) {
if (classes[name]) {
return classes[name];
}
// old impl
}
This will ignore the config if a "real" implementation exists, but keep using the legacy behavior if you haven't replaced the class. If it is, you can implement createClass more like:
function createClass(name, config) {
if (classes[name]) {
return new classes[name](config);
}
// old impl
}
and pass the config arguments into the class ctor. In this case, you may want to filter out function properties (methods) first, as the class probably implements them already. Something like:
function createClass(name, config) {
if (classes[name]) {
let fields = Object.keys(config).filter(key => {
return typeof config[key] !== 'function';
}).map(key => config[key]);
return new classes[name](fields);
}
// old impl
}