The Ember object model provides support for dynamically resolving properties in an object. In other words, if we request the value of a property from an Ember object, this object has the chance to respond to the request even if the property doesn’t exist.
This can easily be implemented using the handler
unknownProperty
.
The code documentation tells us that
Gets the value of a property on an object. If the property is computed,
the function will be invoked. If the property is not defined but the
object implements the unknownProperty
method then that will be invoked.
Let’s see an example of how it works
var MyClass = Ember.Object.extend({
unknownProperty(key) {
return `Hi ${key}!`;
}
});
var object = MyClass.create({});
object.get('Santiago'); // => "Hi Santiago!"
Likewise, you can define the handler
setUnknownProperty
which is called when we try to write in a property.
The code documentation tells us that
Sets the value of a property on an object, respecting computed properties and notifying observers and other listeners of the change. If the property is not defined but the object implements the
setUnknownProperty
method then that will be invoked as well.
Here is an example that implements a simple proxy which puts the strings in lowercase.
var MyClass = Ember.Object.extend({
setUnknownProperty(key, value) {
var content = this.get('content');
if (Ember.typeOf(value) === 'string') {
value = value.toLowerCase();
}
content.set(key, value);
return value;
},
unknownProperty(key) {
var content = this.get('content');
return content.get(key);
}
});
var object = MyClass.create({
content: Ember.Object.create({})
});
object.set('value', 'HELLO WORLD'); // => "hello world"
object.get('value'); // => "hello world"
object.set('anotherValue', 10); // => 10
object.get('anotherValue'); // => 10
As we can see in the example, we create an object that intercepts the values
that we want to write and stores them in the object content
. It also converts
the text to lowercase.
Note that these two handlers are only invoked when there is no property with that name.
This behavior of the Ember object model is very powerful since it allows you to implement proxies very easily. This ability to add a level of indirection when reading or writing attributes can be used to integrate other browser libraries or APIs with Ember.
localStorage
Example
Let’s look at an example in which we will use this functionality to create a
service that allows configurations to easily persist using localStorage
.
First, we need to create the service that communicates with localStorage
,
which we’ll name app/services/preferences.js
import Ember from 'ember';
function read(key) {
return window.localStorage.getItem(key);
}
function write(key, value) {
window.localStorage.setItem(key, value);
}
export default Ember.Service.extend({
unknownProperty(key) {
return read(key);
},
setUnknownProperty(key, value) {
write(key, value);
return value;
}
});
We’ll use the application route to test this mechanism.
We create the controller app/controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
preferences: Ember.inject.service(),
text: Ember.computed.alias('preferences.text'),
actions: {
createPreference() {
this.set('preferences.text', 'Click!');
}
}
});
And then the template
<h2 id="title">Welcome to Ember</h2>
{{input value=text}}
<button {{action 'createPreference'}}>Save</button>
{{outlet}}
Now every time we write in the text box or press the button, the value of the
text
property will be stored in localStorage
. And if we reload the page, it
will show the last value entered!
In the image below, we can see that when the page loads, we don’t have any
value in localStorage
Then, once we’ve written in the text box, it will update the value in
localStorage
thanks to the computed property Ember.computed.alias
.
Finally, if we press the button, it will write directly in the configuration.
If you look closely at the last screenshot, you’ll see that after pressing the
button, the text box was not updated. This is due to the fact that after
successfully responding in the hook setUnknownProperty
, we must notify that
the property changed. To do this we use the method notifyPropertyChanged
.
import Ember from 'ember';
function read(key) {
return window.localStorage.getItem(key);
}
function write(key, value) {
window.localStorage.setItem(key, value);
}
export default Ember.Service.extend({
unknownProperty(key) {
return read(key);
},
setUnknownProperty(key, value) {
write(key, value);
this.notifyPropertyChanged(key);
return value;
}
})
Following this change, the application behaves as expected.