Security News
RubyGems.org Adds New Maintainer Role
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
knockout-decorators
Advanced tools
Decorators for use Knockout JS in TypeScript and ESNext environments
Decorators for use Knockout JS in TypeScript and ESNext environments
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;
}
}
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)
@observable(options: { deep?: boolean, expose?: boolean });
@observable;
By default, shallow observable will be created
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
If { deep: true }
option is provided then all nested object properties
are recursively converted to @observable
import { observable } from "knockout-decorators";
class ViewModel {
@observable({ deep: true })
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
});
If { expose: true }
option is provided then hidden ko.observable
will be
exposed as non-enumerable property with same name prefixed by _
.
import { observable } from "knockout-decorators";
class Model {
@observable({ expose: true })
field = 123;
};
const model = new Model();
const hiddenObservable = model._field; // ko.observable
Accessor decorator that wraps ES6 getter to hidden ko.computed
or ko.pureComputed
@computed(options: { pure: boolean });
@computed;
By default it creates 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);
}
@computed({ pure: false })
get initials() {
return this.firstName.substr(0, 1) + "." + this.LastName.substr(0, 1)+ ".";
}
}
let person = new Person();
ko.pureComputed(() => person.fullName).subscribe(console.log.bind(console));
person.fullName = " John Smith " // [console] ➜ "John Smith"
Property decorator that creates hidden ko.observableArray
with ES6 getter and setter for it
@observableArray(options: { deep?: boolean, expose?: boolean });
@observableArray;
By default, shallow observableArray will be created
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:
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 }]
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
});
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
Apply extenders to decorated @observable
, @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 };
}
}
Caveats
@extend({ notify: "always" })
will not work with: subscribe()
function.
Instead we can use unwrap()
. But other extenders should work.
const vm = new ViewModel();
// this subscription will run only when `vm.first` actually changed
subscribe(() => vm.first, (val) => { console.log(val); });
// use `unwrap()` function to get RAW ko.observable()
unwrap(vm, "first").subscribe((val) => { console.log(val); });
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);
Argument | Default | Description |
---|---|---|
name | Name of component | |
template | "<!---->" | Knockout template definition |
styles | Ignored 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
});
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);
}
}
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 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;
Argument | Default | Description |
---|---|---|
dependencyOrEvent | (1) Function for getting observeble property (2) @event property | |
callback | Callback that handle dependency changes or @event notifications | |
options | null | Options object |
options.once | false | If 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" });
}
}
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")
}
}
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>;
Argument | Default | Description |
---|---|---|
instance | Decorated class instance | |
key | Name of @observable property |
Using { expose: true }
:
import { observable, extend } from "knockout-decorators";
class MyViewModel {
@extend({ required: "MyField is required" })
@observable({ expose: true })
myField = "";
checkMyField() {
alert("MyField is valid: " + this._myField.isValid());
}
}
<div>
<input type="text" data-bind="value: myField"/>
<button data-bind="click: checkMyField">check</button>
<p data-bind="validationMessage: _myField"></p>
</div>
Using unwrap()
:
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);
}
// from TypeScript 2.1 you can use keyof
// to restrict to keys of the given type
unwrap(key: keyof MyViewModel){
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>
Mixin that injects to class shorthands for utility functions and provides automatic disposing of created subscriptions (see MDN or TypeScript 2.2 docs)
function Disposable(Base? /* optional */) {
return class extends Base {
subscribe(...): KnockoutSubscription;
dispose(): void;
unwrap(propName: string): KnockoutObservable;
}
}
Disposable.subscribe(...)
Shorthand for subscribe()
utility function that also store created subscription in hidden class property.Disposable.dispose()
Automatically dispose all subscriptions created by Disposable.subscribe(...)
method.Disposable.unwrap()
Shorthand for unwrap()
utility function that returns hidden Knockout observable for decorated class property.import { observable, computed, Disposable } from "knockout-decorators";
class Derived extends Disposable(Base) {
@observable text = "";
@computed({ pure: false })
get upperCase() {
return this.text.toUpperCase();
}
constructor() {
super();
// subscribe to computed changes
// and store created subscription in hidden class property
this.subscribe(() => this.upperCase, (value) => {
console.log(value);
});
}
dispose() {
// dispose all subscriptions that created by this.subscribe()
super.dispose();
// unwrap and dispose hiddden Knockout computed
this.unwrap("upperCase").dispose();
}
}
// Base class is optional
class Component extends Disposable() { }
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 = "";
}
}
FAQs
Decorators for use Knockout JS in TypeScript and ESNext environments
The npm package knockout-decorators receives a total of 0 weekly downloads. As such, knockout-decorators popularity was classified as not popular.
We found that knockout-decorators demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
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.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.
Security News
Research
Socket's threat research team has detected five malicious npm packages targeting Roblox developers, deploying malware to steal credentials and personal data.