DoneJS StealJS jQuery++ FuncUnit DocumentJS
6.6.1
5.33.3 4.3.0 3.14.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
  • API Docs
    • Observables
      • can-bind
      • can-compute
      • can-debug
      • can-deep-observable
      • can-define
      • can-define/list/list
      • can-define/map/map
      • can-define-backup
      • can-define-stream
      • can-define-stream-kefir
      • can-event-queue
      • can-kefir
      • can-list
      • can-map
      • can-map-compat
      • can-map-define
      • can-observable-array
      • can-observable-object
      • can-observation
      • can-observation-recorder
      • can-observe
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
      • can-value
    • Views
      • can-attribute-observable
      • can-component
        • define
          • extend
          • tag
          • view
          • ViewModel
        • create
          • <tag bindings...>
          • new Component
        • elements
          • <can-slot>
          • <can-template>
        • lifecycle hooks
          • connectedCallback
        • deprecated
          • beforeremove
          • <content>
          • events
          • helpers
          • leakScope
          • viewModel
      • can-observable-bindings
      • can-stache
      • can-stache-bindings
      • can-stache-converters
      • can-stache-element
      • can-stache-route-helpers
      • can-view-autorender
      • can-view-callbacks
      • can-view-import
      • can-view-live
      • can-view-model
      • can-view-parser
      • can-view-scope
      • can-view-target
      • steal-stache
    • Data Modeling
      • can-connect
      • can-connect-ndjson
      • can-connect-tag
      • can-define-realtime-rest-model
      • can-define-rest-model
      • can-fixture
      • can-fixture-socket
      • can-local-store
      • can-memory-store
      • can-ndjson-stream
      • can-query-logic
      • can-realtime-rest-model
      • can-rest-model
      • can-set-legacy
      • can-super-model
    • Routing
      • can-deparam
      • can-param
      • can-route
      • can-route-hash
      • can-route-mock
      • can-route-pushstate
    • JS Utilities
      • can-assign
      • can-define-lazy-value
      • can-diff
      • can-globals
      • can-join-uris
      • can-key
      • can-key-tree
      • can-make-map
      • can-parse-uri
      • can-queues
      • can-string
      • can-string-to-any
    • DOM Utilities
      • can-ajax
      • can-attribute-encoder
      • can-child-nodes
      • can-control
      • can-dom-data
      • can-dom-events
      • can-dom-mutate
      • can-event-dom-enter
      • can-event-dom-radiochange
      • can-fragment
    • Data Validation
      • can-type
      • can-validate
      • can-validate-interface
      • can-validate-legacy
      • can-validate-validatejs
    • Typed Data
      • can-cid
      • can-construct
      • can-construct-super
      • can-data-types
      • can-namespace
      • can-reflect
      • can-reflect-dependencies
      • can-reflect-promise
      • can-types
    • Polyfills
      • can-symbol
      • can-vdom
    • Core
    • Infrastructure
      • can-global
      • can-test-helpers
    • Ecosystem
    • Legacy
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

ViewModel

  • Edit on GitHub

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 ViewModels.
  • Introduce how to communicate between the view and ViewModel and how to communicate between multiple ViewModels.

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 ViewModels likely contain the majority of logic in a CanJS application. Care should be taken to write ViewModels 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.

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 6.6.1.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news