ViewModel
Defines a class used to provide values and methods to the component’s view. The class is initialized with values specified by the component element’s data bindings.
Object
An object that will be passed to DefineMap.extend and used to create class. Instances of that class are accessible by the component’s view.
In the following example, the object set a ViewModel
is used to create a DefineMap:
<my-tag></my-tag>
<script type="module">
import {Component} from "can";
Component.extend({
tag: "my-tag",
ViewModel: {
message: {default: "Hello there!"}
},
view: `<h1>{{message}}</h1>`
});
var viewModelInstance = document.querySelector("my-tag").viewModel;
console.log(viewModelInstance) //-> MyTagVM{message: "Hello there!"}
</script>
This ViewModel is equivalent to the ViewModel created in the following class
example.
class
A class or constructor function (usually defined by DefineMap.extend, or observe.Object) that will be used to create a new observable instance accessible by the component’s view.
For example, every time <my-tag>
is found, a new instance of MyTagViewModel
will
be created:
<my-tag></my-tag>
<script type="module">
import {Component, DefineMap} from "can";
const MyTagViewModel = DefineMap.extend( "MyTagViewModel", {
message: {default: "Hello there!"}
} );
Component.extend({
tag: "my-tag",
ViewModel: MyTagViewModel,
view: `<h1>{{message}}</h1>`
});
var viewModelInstance = document.querySelector("my-tag").viewModel;
console.log(viewModelInstance) //-> MyTagViewModel{message: "Hello there!"}
</script>
Use element.viewModel
to read a component’s view-model instance.
Background
Before reading this documentation, it's useful to have read the Technology Overview and HTML guides.
Use
A component's ViewModel
defines the logic of the component. The ViewModel
has the
methods and properties that the view
reads to update the DOM. ViewModel
are arguably
the most important part of a CanJS application to understand and write well.
On this page we will:
- Cover the basics of how a Component uses the
ViewModel
property. - Explain the importance of writing maintainable and testable
ViewModel
s. - Introduce how to communicate between the
view
andViewModel
and how to communicate between multipleViewModel
s.
How Component uses the ViewModel
property
Component’s ViewModel property is used to define a class. Instances of that
class will be created and used to render the component’s view. For example, the following
defines a MyCounterVM
DefineMap class and sets it as the ViewModel
:
<my-counter count:from="2"></my-counter>
<script type="module">
import {DefineMap, Component} from "can";
const MyCounterVM = DefineMap.extend( {
count: {default: 0},
increment(){
this.count++;
}
} );
Component.extend( {
tag: "my-counter",
view: `
Count: {{this.count}}.
<button on:click="this.increment()">+1</button>
`,
ViewModel: MyPaginateViewModel
} );
</script>
The class can be created separately as above or defined inline as follows:
<my-counter count:from="2"></my-counter>
<script type="module">
import {Component} from "can";
Component.extend( {
tag: "my-counter",
view: `
Count: {{this.count}}.
<button on:click="this.increment()">+1</button>
`,
ViewModel: {
count: {default: 0},
increment(){
this.count++;
}
}
} );
</script>
When a <my-counter>
element is created like:
<my-counter count:from="2"></my-counter>
... component creates an instance of the ViewModel
by passing it any binding values. In this
case, the component will create an instance of MyCounterVM
like:
const viewModel = new MyCounterVM({
count: 2
});
Component will then pass that component to the view
like:
const view = stache(`
Count: {{this.count}}.
<button on:click="this.increment()">+1</button>
`);
view(viewModel) //-> HTML
That HTML
result of calling the view
is inserted within the component
element. Read more about this on the view's documentation.
It's important to notice that each component element will create a new and different instance
of the ViewModel
. For example, the following creates three <my-counter>
elements,
each with unique state. Click the +1 button and notice that each <my-counter>
has its own count
.
<p><my-counter count:from="1"></my-counter></p>
<p><my-counter count:from="2"></my-counter></p>
<p><my-counter count:from="3"></my-counter></p>
<script type="module">
import {Component} from "can";
Component.extend( {
tag: "my-counter",
view: `
Count: {{this.count}}.
<button on:click="this.increment()">+1</button>
`,
ViewModel: {
count: {default: 0},
increment(){
this.count++;
}
}
} );
</script>
Writing maintainable and testable ViewModels
Component ViewModel
s likely contain the majority of logic
in a CanJS application. Care should be taken to write
ViewModel
s in a maintainable and testable manner. We strongly encourage
people to read the Logic and Testing guides that go into
detail on how to write ViewModels.
In short, CanJS supports two different models of writing ViewModels: imperative vs reactive.
Imperative ViewModels mutate properties like the <my-counter>
example above:
increment(){
this.count++;
},
decrement(){
this.count--;
}
Imperative programming is simple and easy for beginners to get started. However, it
can make reasoning about the nature of your application more difficult. For instance,
there might be many other functions that change count. Understanding how count
changes
and what caused a particular count
to change can be hard.
Reactive ViewModels derive their properties from events. For example, the following
derives count
reactively:
<p><my-counter count:from="1"></my-counter></p>
<p><my-counter count:from="2"></my-counter></p>
<p><my-counter count:from="3"></my-counter></p>
<script type="module">
import {Component} from "can";
Component.extend( {
tag: "my-counter",
view: `
Count: {{this.count}}.
<button on:click="this.dispatch('increment')">+1</button>
<button on:click="this.dispatch('decrement')">-1</button>
`,
ViewModel: {
count: {
value({listenTo, lastSet, resolve}) {
listenTo(lastSet, resolve);
let count = resolve(lastSet.value || 0);
listenTo("increment", function( {value} ){
resolve(count++);
});
listenTo("decrement", function( {value} ){
resolve(count--);
});
}
}
}
} );
</script>
This style of programming is more cumbersome. But it can reduce errors dramatically in complex code.
We encourage both styles to be used in apps and even within the same ViewModel! Use Imperative techniques for managing simple code and Reactive techniques for more complex code.
Communication with ViewModels
ViewModels rarely exist in isolation. Instead ViewModels are constantly changing due to various actions:
- A user clicked a button or typed something into the DOM and the ViewModel needs to update.
- A different ViewModel changed and wants to update another ViewModel.
can-stache-bindings has lots of documentation on how to facilitate these forms of communication.
ViewModels also need to update the DOM when when their state changes. can-stache has lots of documentation on how to update the DOM to present a ViewModel's state.