I have a list of items in a component:
list: Array<MyType>;
The user can select and deselect elements on click:
toggleItem(item: MyType) {
if (this.selection.has(item)) {
this.selection.delete(item);
return;
}
this.selection.add(item);
}
The selected items are stored in a Set:
selected: Set<MyType> = new Set();
Now I need to toggle a CSS class and a title-attribute depending whether an element is selected or not:
<button class="toggle"
type="button"
[ngClass]="{'selected': selection.has(item)}"
[title]="selection.has(item) ? 'Select' : 'Deselect'"
(click)="toggleItem(item)">
{{ item.title }}
</button>
Now I've read somewhere that it is a bad idea, to evaluate function calls as Angular periodically will call them for change detection, like here:
[ngClass]="{'selected': selection.has(item)}"
They say that it's better to check a variable or a member of the object instead, like:
[ngClass]="{'selected': item.selected}"
Is is true and does it decrease performance the way I use it currently? Should I add a property to each item that is set when it's added to or removed from the Set?
Whenever Angular performs change detection, it checks whether anything that's variable and is in the template has changed.
Now, the check is pretty straight forward in case of model variables as Angular can simply read values of them in order to check for a change.
But that's not really the case with a function. With functions, Angular can only determine if the variable has changed by calling the function itself.
Now it doesn't make much of a difference if the function is a one-liner return of a value.
But in case the function has complex logic, it will essentially kill all the performance. As every time the change detection cycle runs, Angular will call that function in order to check for changes and detect them.
Hence, it's not really recommended to write functions in data-binding syntax in templates.
NOTE: I've also written a Medium Article about this. I'd suggest you check it out. It's mainly about Angular Reactive Forms performance. But it will help you better understand what I said above.
Angular 2 RC4 app written in TypeScript 1.9 and RxJS 5 beta 6
I'm having a really hard time understanding why my class variable is not passed to the Observable generating function when I subscribe. My code is below but you can see it running at this Plunker (see app/app.component.ts
//this is the problematic var. Can't pass its updated value to the Observable
input = 'nothing'; //<- default value
ngOnInit(){//<- this runs right after class constructor
//set to the value we want to send to the server
this.input = 'something';
}
start(){
//to make sure input = something when we subscribe
this.print("Subscribing when input = "+this.input);
this.source.subscribe(d=>this.print(d));
}
//Simulate sending input to the server
source = this.queryServer(this.input)
.do(()=>this.print('Emitting new value...'));
//server just returns what it received
queryServer(input){return Observable.from(['Server sees '+input])}
print(value){this.log.push(value);}
log=[];
output when I start the subscription by calling start()
Subscribing when input = something
Emitting new value...
Server sees nothing
How come even though the class property is something when we call subscribe, nothing is passed to queryserver()? I tried making input an object such as input={d:'something'} thinking that maybe the problem had to do with passing values instead of references but the result was the same.
It's happening because you are passing the default value of input to queryServer when you construct the component. Even though you change it in ngInit it has already been used to construct the pipeline that you then subscribe to. You are essentially doing the following:
this.input = "nothing";
const copyOfInput = this.input;
this.input = "something";
console.log(copyOfInput); //-> nothing
You have changed what this.input is pointing at but copy of it you made still points to the old value. I would posit that you actually want to initialize the pipeline in ngInit and not as part of the construction of the object.
Thanks to #paulpdaniels for the helpful input. He suggested that I compose the source in ngOnInit, after I have set input=something. That works for the first value of input but in practice input will continue to change after ngOnInit is complete, so the source will once again emit obsolete data.
What still really surprises me is that even when the input is an object (rather than a simple string), the problem in the OP persists. Since objects are assigned by reference in JS, I expected differently.
The code design I opted for is to compose source inside a setter for input. That way, each time that value is set, source is refreshed:
source:Observable<any>;
_input = 'nothing';
get input(){return this._input;}
set input(val){
this._input = val;
source = this.queryServer(val)
.do(()=>this.print('Emitting new value...'));
}
Still feels hack-y to me but I'm brand new to Observables so maybe there is no better way. If there is, I hope someone will teach it to me.
Say I have an object similar to the following:
Cars = {
"car_one": {
data: {
make: "Ford",
model: "Festiva"
},
img: "car_one.jpg"
},
"car_two": {
data: {
make: "Chevy",
model: "Pinto",
color: "Green"
},
img: "car_two.jpg"
}
...and so on...
}
I am hoping to find a way to monitor just the data property (and all sub-properties) for changes, then be able to setup a function to fire.
I have been looking at Object.watch() but testing seems to show that I would need to have it manually setup to check each property of Cars.{some_car}.data which (as shown in my example) isn't a constant set. I guess I'm hoping there's a less complex method that I'm just not aware of.
Depending on how much control you have over how its called, you could make an accessor function. The function could take an object as an argument, and set that object as the current value if the argument is set. For your use you can add a trigger of some type here. If no argument is passed it would return the current value.
Thats the type of API that knockout uses to great effect.
In modern browsers, getters and setters make this easy. For older values you could have an accessing property and then another property that serves as the holder for the data.
Unfortunately, as of today the easiest way would probably be to loop over the properties in data and call Object.watch for each of them. However, we might have Object.observe at the rescue eventually. If you are looking for a cross-browser Obect.watch implementation, have a look here.
Other alternatives would be to wrap your data in an observable object that allows to modify the data through a generic set method that would fire events when some properties are modified or to have some property watching task that would execute at a specific interval or triggered by specific actions that would check the object for changes. However, these alternatives are not any simpler.
I was trying to keep my code a bit cleaner by using the OOP patterns that MooTools provides in combination with Knockout.js
I can't seem to get it to work as Knockout returns some function (the property function, meh?) when it data binds the values. If the values are retrieved with javascript however, it works as exprected. I made a quick jsFiddle so you can test it out:
http://jsfiddle.net/tixz/JK2jt/
Thanks in advance!
In your current structure, the observables are created on the prototype of your class. Knockout does not intend on them being on the prototype (they can't be shared as they keep private state), and an internal function ko.isObservable fails, because of it. This causes the unwrapping code in KO does not realize that it needs to retrieve the underlying value for your observables.
You would have to write it like:
var ViewModel = new Class({
initialize: function(secondVal){
this.someProp = ko.observable('Here is my value');
this.anotherProp = ko.observable(secondVal);
}
});
http://jsfiddle.net/rniemeyer/JK2jt/9/
I'm still learning the proper usage of Knockout and I've found myself quickly getting away from ever typing ko.observable when setting up my viewmodel and instead just defining an object literal and passing it through the mapping plugin with something like
var viewModel = ko.mapping.fromJS(data);
or at the very least, something along the lines of stuffing all of my data into an attribute on the viewModel like so
var viewModel = {
... events etc ... ,
"data": ko.mapping.fromJS(data)
}
To be honest, the main reason I've been doing this is to get around having to type ko.observable and ko.observableArray repetitively. I'm just trying to figure out if this is a good approach and if there are any downsides to dropping the specific var x = ko.observable() declaration all together. Also, I'm doing this all on load, not in response to any ajax call etc, which from what I can tell, is what the mapping plugin was designed for.
In your work with knockout, do you still declare the observables manually, one by one, or have you gone with the mapping.fromJS method that I use? Are there any specific downsides to using the mapping plugin so frequently like this?
Edit:
Specific Example
In this article, Steve sets up his viewModel by doing
var initialData = [ { ... } , { ... } ]; // json from the serializer
var viewModel = {
gifts : ko.observableArray(initialData)
};
Normally, I'd just use ko.mapping.fromJS for this situation as well, specifically to make sure the objects within the array are turned into observables as well. Looking at what he did, my approach seems like its overkill and adds a bit of unnecessary overhead.
After using Knockout for a little longer, I've noticed that the mapping plugin has some additional options that give you much more fine grained control over the mapping process.
Control type and amount of properties generated
There are several ways to accomplish this, and I'll go over some, but the end result is that you end up with a lighter result from the mapping plugin because everything isn't observable.
Basically you leave everything that you don't think will change, as a normal property and only make observables out of the specific items that you want to observe.
Make mapping omit certain properties
You can make the mapping plugin omit properties entirely from the end result by specifying things like ignore or include. Both of these accomplish the same thing, just in opposite ways.
Note: Samples are from the knockout.js mapping plugin documentation, comments added by me
Mapping Plugin Argument: include
The following snippet will omit all properties from the source object other than those passed in via the include argument.
// specify the specific properties to include as observables in the end result
var mapping = {
// only include these two properties
'include': ["propertyToInclude", "alsoIncludeThis"]
}
// viewModel will now only contain the two properties listed above,
// and they will be observable
var viewModel = ko.mapping.fromJS(data, mapping);
Mapping Plugin Argument: ignore
If you want to only omit certain properties from the source object, use the ignore argument as shown below. It will make observables from all properties in the source object except for the specified properties.
// specify the specific properties to omit from the result,
// all others will be made observable
var mapping = {
// only ignore these two properties
'ignore': ["propertyToIgnore", "alsoIgnoreThis"]
}
// viewModel will now omit the two properties listed above,
// everything else will be included and they will be an observable
var viewModel = ko.mapping.fromJS(data, mapping);
Control what properties are or are not made observable
If you need to include properties but you don't think that they will need to be made observable (for whatever reason), the mapping plugin has something that can help.
Mapping Plugin Argument: copy
If you want the mapping plugin to simply copy the plain properties and not make them observable, use this argument, as shown below.
// tell the mapping plugin to handle all other properties normally,
// but to simply copy this property instead of making it observable
var mapping = {
'copy': ["propertyToCopy"]
}
var viewModel = ko.mapping.fromJS(data, mapping);
Gain complete control over the mapping process
If you want to have 100% control over what is created in the mapping process, including the ability to put closures and subscriptions in your objects, then you want to use the "create" option.
plain result with calculated properties
Here is an example where I was mapping data from an ajax call to an object with a results property. I didn't want anything observable and I just wanted a simple generated property that would be made of the other simple properties on the object. Maybe not the most compelling example but it demonstrates the functionality.
var searchMappingConfig = {
// specific configuration for mapping the results property
"results": {
// specific function to use to create the items in the results array
"create": function (options) {
// return a new function so we can have the proper scope/value for "this", below
return new function () {
// instead of mapping like we normally would: ko.mapping.fromJS(options.data, {}, this);
// map via extend, this will just copy the properties from the returned json element to "this"
// we'll do this for a more light weight vm since every last property will just be a plain old property instead of observable
$.extend(this, options.data);
// all this to add a vehicle title to each item
this.vehicleTitle = this.Year + "<br />" + this.Make + " " + this.Model;
}, this);
};
}
}
}
subscriptions and closures and mapping, oh my
Another situation is if you want closures and subscriptions in your result. This example is too long to be included in its entirety but its for a vehicle make/model hierarchy. I wanted all the models (children) for a given make (parent) to be un-enabled if the model was un-enabled and I wanted this to be done with a subscription.
// here we are specifying the way that items in the make array are created,
// since makes has a child array (Models), we will specify the way that
// items are created for that as well
var makesModelsMappingConfig = {
// function that has the configuration for creating makes
"create": function (options) {
// return a new function so we can have the proper
// scope/value for "this", below
return new function () {
// Note: we have a parent / child relationship here, makes have models. In the
// UI we are selecting makes and then using that to allow the user to select
// models. Because of this, there is going to be some special logic in here
// so that all the child models under a given make, will automatically
// unselect if the user unselects the parent make.
// make the selected property a private variable so it can be closure'd over
var makeIsSelected = ko.protectedComputed(false);
// expose our property so we can bind in the UI
this.isSelected = makeIsSelected;
// ... misc other properties and events ...
// now that we've described/configured how to create the makes,
// describe/configure how to create the models under the makes
ko.mapping.fromJS(options.data, {
// specific configuration for the "Models" property
"Models": {
// function that has the configuration for creating items
// under the Models property
"create": function (model) {
// we'll create the isSelected as a local variable so
// that we can flip it in the subscription below,
// otherwise we wouldnt have access to flip it
var isSelected = ko.protectedComputed(false);
// subscribe to the parents "IsSelected" property so
// the models can select/unselect themselves
parentIsSelected.current.subscribe(function (value) {
// set the protected computed to the same
// value as its parent, note that this
// is just protected, not the actual value
isSelected(value);
});
// this object literal is what makes up each item
// in the Models observable array
return {
// here we're returning our local variable so
// we can easily modify it in our subscription
"isSelected": isSelected,
// ... misc properties to expose
// under the item in the Model array ...
};
}
}
}, this);
};
}
};
All in all, what I've found is that you rarely need 100% of an object that you'd pass to the plugin and you rarely need 100% of it to be observable. Dig in with the mapping configuration options and create all sorts of complex and simple objects. The idea is to only get everything you need, nothing more or less.
My suggestion to you would the same another questioned I just answered at https://stackoverflow.com/questions/7499133/mapping-deeply-hierarchical-objects-to-custom-classes-using-knockout-mapping-plug.
Your reasoning for using mapping plug-in is reasonable and the one that I use. Why type more code than you have to?
In my experience with knockout (all of 4 months), I've found that the less I do manually and let the knockout routines do their thing, the better my apps seem to run. My suggestion is try the simplest approach first. If it doesn't meet your needs, look at how the simple approach is doing it's "thing" and determine what has to change to meet your needs.
Allen, my recent learning experience with Knockout.js has been similar to yours. We work with a deep hierarchical object graph from the server and I have defined explicit instantiable view model functions which preserve the basic structure of it.
I began by defining each property explicitly as an observable on the relevant view model, but that quickly got out of hand. Also, a major reason for switching to using the mapping plugin was that we have to do frequent Ajax posts of the graph back to the server where it is merged with the persisted version, then validated on the server in such a way that numerous properties can change and collections be modified, and a new instance returned as the Ajax result where it has to be re-merged with the client representation. That became seriously difficult, and the mapping plugin helped big time by allowing the specification of identifiers for resolving adds / deletes / updates and to remap an updated graph onto the original.
It also helped in the original graph creation through the use of the "create" option for sub view models. In each view model constructor I receive a reference to the parent view model plus the data with which to construct the child view model, then create further mapping options to create grandchildren from the passed-in child data.
The only (slight) downside I recently found, as detailed in this question, is that when doing ko.mapping.toJSON it doesn't hook into any toJSON overrides you may have defined on the prototypes of your view models in order to exclude properties from serialization. I have been able to get around that by specifying ignore options in the unmapping, as recommended by Ryan Niemeyer in that post.
So in summary, I'll definitely be sticking with the mapping plugin. Knockout.js rules.
A simpler but help-full add-on could be knockout-data-projections
Currently, it does not handle js to viewmodel mappings, but it handles quite well view model to JS mappings.