Socket
Socket
Sign inDemoInstall

knockout-decorators

Package Overview
Dependencies
1
Maintainers
1
Versions
25
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    knockout-decorators

Decorators for use Knockout JS in TypeScript and ESNext environments


Version published
Maintainers
1
Created

Changelog

Source

[0.9.1] - 2017-02-01

Added

  • @reactive (deep observable) decorator. It can be used for recursively  observing nested object's properties (see #3)
  • @event decorator. It creates hidden ko.subscribable for class property
  • ObservableArray.set(index, value) method
  • ObservableArray.mutate(() => {...}) method

Changed

  • subscribe() function can be used with @event decorator
  • subscribe() function can subscribe to "arrayChange" Knockout event

Readme

Source

Knockout Decorators

Decorators for use Knockout JS in TypeScript and ESNext environments

Build Status GitHub license npm version

Example

import { observable, computed, component } from "knockout-decorators";

@component("person-view", `
  <div>Name: <span data-bind="text: fullName"></span></div>
  <div>Age: <span data-bind="text: age"></span></div>
`)
class PersonView {
  @observable firstName: string;
  @observable lastName: string;
  @observable age: string;
  
  @computed get fullName() {
    return this.firstName + " " + this.lastName;
  }
  
  constructor({ firstName, lastName, age }, element, templateNodes) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
}

Documentation

Work with KnockoutValidation

Usage without module loaders

Changes from v0.7.1

@observable

Property decorator that creates hidden ko.observable with ES6 getter and setter for it
If initialized by Array then hidden ko.observableArray will be created (see @observableArray)

import { observable } from "knockout-decorators";

class Model {
  @observable field = 123;
  @observable collection = [];
};
let model = new Model();

ko.computed(() => { console.log(model.field); }); // [console] ➜ 123
model.field = 456;                                // [console] ➜ 456

@computed

Accessor decorator that wraps ES6 getter to hidden ko.pureComputed
Setter is not wrapped to hidden ko.pureComputed and stays unchanged

import { observable, computed } from "knockout-decorators";

class Person {
  @observable firstName = "";
  @observable lastName = "";

  @computed
  get fullName() { return this.firstName + " " + this.lastName; }
  set fullName(value) { [this.firstName, this.lastName] = value.trim().split(/\s+/g); }
}
let person = new Person();

ko.pureComputed(() => person.fullName).subscribe(console.log.bind(console));

person.fullName = "  John  Smith  " // [console] ➜ "John Smith"

@reactive

Like @observable, but creates "deep observable" property (see example below)
If initialized by Array then hidden ko.observableArray will be created (see @observableArray)

import { reactive } from "knockout-decorators";

class ViewModel {
  @reactive deepObservable = {  // like @observable
    firstName: "Clive Staples", // like @observable
    lastName: "Lewis",          // like @observable

    array: [],                  // like @observableArray

    object: {                   // like @observable
      foo: "bar",               // like @observable
      reference: null,          // like @observable
    },
  }
}

const vm = new ViewModel();

vm.deepObservable.object.reference = {
  firstName: "Clive Staples", // make @observable
  lastName: "Lewis",          // make @observable
};

vm.deepObservable.array.push({
  firstName: "Clive Staples", // make @observable
  lastName: "Lewis",          // make @observable
});

@observableArray

Property decorator that creates hidden ko.observableArray with ES6 getter and setter for it

import { observableArray } from "knockout-decorators";

class Model {
  @observableArray array = [1, 2, 3];
};
let model = new Model();

ko.computed(() => { console.log(model.field); }); // [console] ➜ [1, 2, 3]
model.field = [4, 5, 6];                          // [console] ➜ [4, 5, 6]

Functions from ko.observableArray (both Knockout-specific remove, removeAll, destroy, destroyAll, replace and redefined Array.prototype functions pop, push, reverse, shift, sort, splice, unshift) are also presents in decorated poperty.
They works like if we invoke them on hidden ko.observableArray.

And also decorated array has:

  • a subscribe(callback: (value: any[]) => void) function from ko.subscribable,
import { observableArray, ObservableArray } from "knockout-decorators";

class Model {
  @observableArray array = [1, 2, 3] as ObservableArray<number>;
};
let model = new Model();
model.array.subscribe((changes) => { console.log(changes); }, null, "arrayChange");

model.array.push(4);                      // [console] ➜  [{ status: 'added', value: 4, index: 3 }]
model.array.remove(val => val % 2 === 0); // [console] ➜  [{ status: 'deleted', value: 2, index: 1 },
                                          //                { status: 'deleted', value: 4, index: 3 }]
  • a new mutate(callback: () => void) function that runs callback in which we can mutate array directly,
import { observableArray, ObservableArray } from "knockout-decorators";

class Model {
  @observableArray array = [1, 2, 3] as ObservableArray<number>;
};

let model = new Model();

model.array.mutate(() => {
  model.array[1] = 200; // this changes are observed
  model.array[2] = 300; // when mutation callback stops execution
});
  • a new set(i: number, value: any): any function that sets a new value at specified index and returns the old value.
import { observableArray, ObservableArray } from "knockout-decorators";

class Model {
  @observableArray array = [1, 2, 3] as ObservableArray<number>;
};

let model = new Model();

let oldValue = model.array.set(2, 300) // this change is observed

console.log(model.array); // [console] ➜ [1, 2, 300]
console.log(oldValue);    // [console] ➜ 3

@extend

Apply extenders to decorated @observable, @reactive, @observableArray or @computed

@extend(extenders: Object);
@extend(extendersFactory: () => Object);

Extenders can be defined by plain object or by calling method, that returns extenders-object.
Note that extendersFactory invoked with ViewModel instance as this argument.

import { observable, computed, extend } from "knockout-decorators";

class ViewModel {
  rateLimit: 50;
  
  @extend({ notify: "always" })
  @observable first = "";

  @extend(ViewModel.prototype.getExtender)
  @observable second = "";

  @extend({ rateLimit: 500 })
  @computed get both() {
    return this.first + " " + this.second;
  }
  
  getExtender() {
    return { rateLimit: this.rateLimit };
  }
}

@component

Shorthand for registering Knockout component by decorating ViewModel class

@component(name: string, options?: Object);
@component(name: string, template: any, options?: Object);
@component(name: string, template: any, styles: any, options?: Object);
ArgumentDefaultDescription
nameName of component
template"<!---->"Knockout template definition
stylesIgnored parameter (used for require() styles by webpack etc.)
options{ synchronous: true }Another options that passed directly to ko.components.register()

By default components registered with synchronous flag.
It can be overwritten by passing { synchronous: false } as options.

If template is not specified then it will be replaced by HTML comment <!---->

If ViewModel constructor accepts zero or one arguments, then it will be registered as viewModel: in config object.

import { component } from "knockout-decorators";

@component("my-component")
class Component {
    constructor(params: any) {}
}
// ▼▼▼ results to ▼▼▼
ko.components.register("my-component", {
    viewModel: Component,
    template: "<!---->",
    synchronous: true,
});

If ViewModel constructor accepts two or three arguments, then createViewModel: factory is created
and { element, templateNodes } are passed as arguments to ViewModel constructor.

import { component } from "knockout-decorators";

@component("my-component",
    require("./my-component.html"),
    require("./my-component.css"), {
    synchronous: false,
    additionalData: { foo: "bar" } // consider non-standard field
})
class Component {
    constructor(
        private params: any,
        private element: Node,
        private templateNodes: Node[]
    ) {}
}
// ▼▼▼ results to ▼▼▼
ko.components.register("my-component", {
    viewModel: {
        createViewModel(params, { element, templateNodes }) {
            return new Component(params, element, templateNodes);
        }
    },
    template: require("./my-component.html"),
    synchronous: false,
    additionalData: { foo: "bar" } // consider non-standard field
});

@autobind

Bind class method to class instance. Clone of core-decorators.js @autobind

import { observable, component, autobind } from "knockout-decorators";

@component("my-component", `
  <ul data-bind="foreach: array">
    <li data-bind="click: $component.remove">remove me</li>
  </ul>
`)
class MyComponent {
  @observable array = [1, 2, 3] as ObservableArray<number>;
  
  @autobind
  remove(item: number) {
    this.array.remove(item);
  }
}

@event

Create subscribable function that invokes it's subscribers when it called.

All arguments that passed to @event function are translated to it's subscribers.
Internally uses hidden ko.subscribable.

Subscribers can be attached by calling .subscribe() method of EventType type or by subscribe() utility.

import { event, EventType } from "knockout-decorators";

class Producer {
  @event myEvent: EventType;
}

class Consumer {  
  constructor(producer: Producer) {
    producer.myEvent.subscribe((arg1, arg2) => {
      console.log("lambda:", arg1, arg2);
    });
    
    // `subscription` type is `KnockoutSubscription`
    const subscription = producer.myEvent.subscribe(this.onEvent);
  }
  
  @autobind
  onEvent(arg1, arg2) {
    console.log("method:", arg1, arg2);
  }
}

const producer = new Producer();
const consumer = new Consumer(producer);

// emit @event
producer.myEvent(123, "test");
// [console] ➜ lambda:  123  "test"
// [console] ➜ method:  123  "test"

subscribe

Subscribe to @observable (or @computed) dependency with creation of hidden ko.computed()

subscribe<T>(
  dependency: () => T,
  callback: (value: T) => void,
  options?: { once?: boolean, event?: string }
): KnockoutSubscription;

Or subscribe to some @event property

subscribe<T1, T2, ...>(
  event: (arg1: T1, arg2: T2, ...) => void,
  callback: (arg1: T1, arg2: T2, ...) => void,
  options?: { once?: boolean }
): KnockoutSubscription;
ArgumentDefaultDescription
dependencyOrEvent(1) Function for getting observeble property (2) @event property
callbackCallback that handle dependency changes or @event notifications
optionsnullOptions object
options.oncefalseIf true then subscription will be disposed after first invocation
optons.event"change"Event name for passing to Knockout native subscribe()

Subscribe to @observable changes

import { observable, subscribe } from "knockout-decorators";

class ViewModel {
  @observable field = 123;
  
  constructor() {
    subscribe(() => this.field, (value) => {
      console.log(value); // TypeScript detects that `value` type is `number`
    });

    subscribe(() => this.field, (value) => {
      console.log(value);
    }, { once: true });

    subscribe(() => this.field, (value) => {
      console.log(value);
    }, { event: "beforeChange" });    
  }  
}

Subscribe to @event property

import { event, subscribe } from "knockout-decorators";

class ViewModel {
  @event myEvent: (arg: string) => void;
  
  constructor() {
    subscribe(this.myEvent, (arg) => {
      console.log(arg); // TypeScript detects that `arg` type is `string`
    });
    
    subscribe(this.myEvent, (arg) => {
      console.log(arg);
    }, { once: true });
    
    // `subscription` type is `KnockoutSubscription`
    const subscription = subscribe(this.myEvent, (arg) => {
      console.log(arg);
    });
    
    // unsubscribe from @event
    subscription.dispose();
    
    // emit @event
    this.myEvent("event argument")
  }  
}

unwrap

Get hidden ko.observable() for property decodated by @observable or hidden ko.pureComputed() for property decodated by @computed

unwrap(instance: Object, key: string | symbol): any;
unwrap<T>(instance: Object, key: string | symbol): KnockoutObservable<T>;
ArgumentDefaultDescription
instanceDecorated class instance
keyName of @observable property

KnockoutValidation example

import { observable, extend, unwrap } from "knockout-decorators";

class MyViewModel {
  @extend({ required: "MyField is required" })
  @observable myField = "";
  
  checkMyField() {
    alert("MyField is valid: " + unwrap(this, "myField").isValid());
  }

  // pass `unwrap` function to data-bindings
  unwrap(key: string) {
    return unwrap(this, key);
  }
}
<div>
  <input type="text" data-bind="value: myField"/>
  <button data-bind="click: checkMyField">check</button>
  <p data-bind="validationMessage: unwrap('myField')"></p>
</div>

Usage without module loaders (in global scope)

layout.html

<script src="/{path_to_vendor_scrpts}/knockout.js"></script>
<script src="/{path_to_vendor_scrpts}/knockout-decorators.js"></script>

script.ts

namespace MyTypescriptNamespace {
  // import from TypeScript namespace (JavaScript global variable)
  const { observable, computed } = KnockoutDecorators; 
  
  export class MyClass {
    @observable field = "";
  }
}

Breaking changes from v0.7.1

  1. Removed @subscribe decorator
  2. Removed @reaction decorator
  3. Added subscribe(() => this.observableProp, (value) => { ... }) function
  4. Added unwrap(this, "observablePropName") function

Native ko.computed with side effects can be used in all places where we use @reaction decorator.

In v0.7.1 and earlier @subscribe decorator can be used only with @observable but not with @computed. To avoid this restriction we can create ko.pureComputed and subscribe to it:

class ViewModel {
  @computed get computedProp() { ... }

  constructor() {
    ko.pureComputed(() => this.computedProp).subscribe((value) => { ... });
  }
}

So from v0.8.0 instead of @subscribe decorator there is shorthand function subscribe with some extra functionality like "subscribe once":

class ViewModel {
  @computed get computedProp() { ... }

  constructor() {
    subscribe(() => this.computedProp, (value) => { ... });
  }
}

Keywords

FAQs

Last updated on 01 Feb 2017

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc