valent
v0.1.0-rc23
Published
Helpfull ES6 runtime for angular
Downloads
25
Readme
Valentjs
Valentjs provide easier way to register framework components (directives / routing / controllers) with features. Registered components could be transalted into different frameworks but right now only AngularJS available.
Valentjs - just the wrapper for frameworks and could be used together with default frameworks approach.
- Valentjs
- Valentjs + AngularJS
- AngularJS bootstrap
- Configuration
- Angular configuration (config/run)
- Controllers
- Route
- Directive
- Defined structures
- Serializers
- Url
- Url Manager
- Services
- Decorators
- Base Components
- Contributing
- TODO
TOC was generated using doctoc.
Valentjs + AngularJS
Valentjs provide methods to register and validate:
- directvies
- controllers
- routing
and features form the box:
- Writing code using ES6
- Easier application configuration
- URL manager
- Configured URL serializer/unserializer
- Custom serializers
- Multiple routing for one controller
- Enchanted tcomb structures
- Interfaces / pipes for directives
- Debounced safe digest
- No access to $scope
Simple examples (valentjs / angularjs)
AngularJS bootstrap
valent - same as angular - variable at the global namespace.
import Angular from 'valent/angular';
let framework = new Angular('your-application-name', {
/**
* if dependencies are defined - angular module
* will be created automatically
*
* Otherwise - you should register angular module
* manually
*/
dependencies: [
'ngRoute'
]
});
valent.bootstrap(framework);
<body ng-app="your-application-name"></body>
Configuration
valent.config
could be used as key/value storage. Keys with dots will be translated into nested objects.
valent.config.set('routing.otherwise', '/home');
valent.config.set('routing.html5', true);
valent.config.get('routing');
// {otherwise: '/home', 'html5': true}
valent.config.get('routing.otherwise') // '/home'
And there are number of shortctus
valent.config.route.otherwise('/home');
valent.config.route.onChangeStart(route => {
// ...
});
valent.config.route.addResolver('schema', () => {
// ...
});
valent.config.exception.handler((error, causedBy) => {
// ...
});
List of config shorctus
Routing
- route.otherwise(url) - setup url for redirect if route does not exist
- route.onChangeStart(callback) - add hook for event $routeChangeStart
- route.onChangeError(callback) - add hook for event $routeChangeError
- route.addResolver(key, resolver) - add global resolver that will be applied for each route
- resolver(name, params) - same as local resolver. Takes 2 arguments - route name, route params.
- route.enableHistoryApi() - enable html5 routing. By default - html5 routing is enabled
- route.disableHistoryApi() - disable html5 routing
- routing.requireBase(isBaseRequired) - is base tag requierd for application
Exceptions
- exception.handler(handler) - setup exception handler that will available for framework's context and window.on('error')
Angular configuration (config/run)
import Angular from 'valent/angular';
let framework = new Angular('your-application-name', {
dependencies: [
'ngRoute'
]
});
// app - angular module
let app = framework.getAngularModule();
// same as with native angular
app.config(['$rootScope', $rootScope => {
// ...
});
// same as with native angular
app.run(['$rootScope', $rootScope => {
// ...
});
Or you can run config/run methods directly to angular.module
import Angular from 'valent/angular';
let framework = new Angular('your-application-name', {
dependencies: [
'ngRoute'
]
});
angular.module('your-application-name')
.config(['$rootScope', $rootScope => {
// ...
});
Controllers
Simple configuration
class HomeController {
// ...
}
valent.controller('home', HomeController);
// or with already attached route
valent.controller('home', HomeController, {
url: '/home'
});
valent.controller
takes 3 arguments
- controller name
- controller class
- options
Controller class
class HomeController {
constructor(resolvers, url, logger) {
// ...
}
destructor() {
// ...
}
}
Constructor could take up to 3 arguments:
resolvers
- if there any local or global resolverurl
- if three is attached routelogger
- configured logger. Always add colored controller's name to logs
destructor
method is called when controller's $scope is destroyed ($destroy event).
Controller options
controller.option.as
An identifier name for a reference to the controller. By default - controller.
<h1>{{ controller.greeting }}</h1>
If as defined:
{
as: '_'
}
Template should be
<h1>{{ _.greeting }}</h1>
controller.option.url
Route url proxy
controller.option.params
Route url proxy
controller.option.resolve
Route resolve proxy
controller.option.struct
Route struct proxy
controller.option.template
Route template proxy
controller.option.templateUrl
Route templateUrl proxy
Controller.render()
If there is static function render() at controller's class - it's result will be used as template.
class HomeController {
constructor() {
// ...
}
static render() {
return '<div>Yo!</div>'
}
}
Route
valent.route('home', '/home', {
template: '<div>...</div>'
});
valent.controller
takes 3 arguments
- controller name
- url
- options
url
url for controller. Could be a string:
{
url: '/home'
}
or array of strings (means that controller will be available by different routes)
{
url: ['/home', '/my/home']
}
Route options
route.option.params
Any params that will be passed to angular route config. Also route params are available in resolvers (local and global) as second argument.
valent.route('home', '/home', {
params: {
guest: true
}
});
route.option.resolve
Local resolvers. Function that will be executed before controller's constructor. Support promises. Resolved results will be passed to controller's constructor. Local resolvers will be executed after global resolvers. Here is Angular documentation about route resolvers. Each resolver take 2 arguments:
name
- resolving route nameparams
- resolving route params
{
resolve: {
'permission': (name, params) => {
return Users.me().catch(() => {
return params.guest
? Promise.resolve('Allowed as guest')
: Promise.reject('Denied for guests');
});
}
}
}
route.option.template
{
template: '<div>Yo!</div>'
}
route.option.templateUrl
{
templateUrl: '/templates/home.html'
}
route.option.struct
Structure for url.
import * as primitives from 'valent/utils/primitives';
class HomeController {
// ...
}
valent.controller(home, HomeController, {
url: '/home',
struct: {
id: primitives.Num,
tags: primitives.MaybeListStr,
period: primitives.MaybeListDat
}
});
Directive
class GreetMeController {
// ...
}
valent.component('greet-me', GreetMeController, {
});
Directive Controller class
Full list of auto called methods. They are NOT required.
class GreetMeController {
constructor(params, logger) {
}
destructor() {
}
link(element, compileResult) {
}
require(controllers) {
}
static compile(element) {
return {};
}
static render() {
return '<div>....</div>';
}
}
Constructor arguments are depends on options. By default constructor takes 2 arguments
- directive params
- logger - configured logger. Always add colored controller's name to logs
if interfaces
or optionals (options)
are defined - they will passed before.
TODO: Find better naming for this features. High prio :)
destructor
method is called when controller's $scope is destroyed ($destroy event).link(element, compileResult)
method - same as default angular's link function but do not take $scope.static compile()
result will be passed as second argument.require(controllers)
method - takes all required controllers. Returned content will be passed as second argument tolink()
method.static render()
- result of this method could be used as directive's template.static compile(element)
method could be used for template compilation. Same as default angular's compile. Very useful if directive's templates are used as configuration. In this way - directive's template should NOT be defined.
For example in this case "Applications" will be used for multi-select label.
<pl-multiselect items="controller.items">
Applications
</pl-multiselect>
For example in this case - cell templates are defined as a content of grid
directive. In static compile(element)
method this could be parsed and passed to directive controller.
<grid>
<column id="network">
<network-icon id="cell.value"></network-icon>
</column>
<column id="date">
{{ cell.value | date:"MM/dd/yyyy" }}
</column>
</grid>
Directive options
directive.option.as
Same as valent.controller as option
directive.option.template
Same as valent.controller template option but not proxy no route.
directive.option.templateUrl
Same as valent.controller templateUrl option but not proxy no route.
directive.option.restrict
Same as angular directive's options restrict.
Recomment to use only
- A - only matches attribute name
- E - only matches element name
directive.option.require
Uses for directive communications.
{
require: ['ngModel', '^^plFilterBar']
}
More details at official angular [doc](https://docs.angularjs.org/api/ng/service/$compile#-require-).
Relate controllers will be passed to require(controllers)
method.
class GreetMeController {
constructor(params, logger) {
}
require(controllers) {
this.ngModel = controllers.ngModel;
this.plFilterBar = controllers.plFilterBar;
}
}
directive.option.params
Same as angular directive's options scope.
directive.option.interfaces
// app-connector.js
class AppConnector {
host = valent.config.get('app.server.host');
constructor(port) {
//...
}
connect() {
}
// ...
}
// server-status-component.js
import AppConnector from './app-connector';
class ServerStatusController {
constructor(connector) {
connector.connect().then(status => {
this.status = status;
});
}
static render() {
return '<div>{{ _.status }}</div>'
}
}
valent.component('server-status', ServerStatusController, {
interfaces: {
connector: AppConnector
}
});
// home-screen.js
import AppConnector from './app-connector';
class HomeController {
connector = new AppConnector(9001);
constructor() {
this.connector.notify();
}
static render() {
return `<server-status connector="controller.connector"></server-status>`;
}
}
valent.controller('home', HomeController);
So we defined for directive <server-status>
interface connector with specific class - AppConnector. So already created instance of this class should be passed to directive as attribute. If not - Exception. If wrong class - Exception.
Bonuses:
- Have control over same object in directive and in parent controller (no matter if it is screen's controller or another directive's controller). Could be useful to create relation between app components.
- Interfaces are passed as first arguments to directitve's constructor
- Inside directive you are sure that instance (or its parents) has correct class (type).
- Useful for complex directives that works with charts, grids, forms etc.
directive.option.options (rename)
valent.component('server-status', ServerStatusController, {
interfaces: {
connector: AppConnector
},
option: {
validator: AppValidator
}
});
Same as interfaces but options are NOT required. If option's attribute is defined at directive - option instance will be passed to directive's constructor.
<server-status connector="controller.connector" validator="controller.connector"></server-status>
class ServerStatusController {
constructor(connector, validator) {
//
}
}
If option's attribute is NOT defined - null will be passed to directive's constructor.
<server-status connector="controller.connector"></server-status>
class ServerStatusController {
constructor(connector, validator) {
equals(validator, null);
}
}
directive.option.pipes
If defined and not passed to directive - will be created automatically. Available throw DirectiveParams.get()
.
// toggler.js
class Toggler extends Events {
isVisible = false;
open() {
if (!this.isVisible) {
this.isVisible = true;
this.emit('open');
}
}
close() {
if (this.isVisible) {
this.isVisible = false;
this.emit('close');
}
}
}
// drop-down-component.js
import Toggler from './toggler';
class DropDownController {
constructor(params) {
this.toggler = params.get('toggler');
}
static render() {
return `
<button
ng-click="controller.toggler.open()">
Open
</button>
<button
ng-click="controller.toggler.close()">
Close
</button>
<div ng-if="controller.toggler.isVisible">
...
</div>`;
}
}
valent.component('drop-down', DropDownController, {
pipes: {
toggler: Toggler
}
});
// home-screen.js
import Toggler from './toggler';
class HomeController {
toggler = new Toggler();
constructor() {
this.toggler.open();
this.toggler.on('open', () => {
console.log('opened :)');
});
}
}
valent.controller('home', HomeController);
<div ng-controller="home">
<drop-down></drop-down>
<drop-down toggler="controller.toggler></drop-down>
</div>
Both cases will work. If you not pass toggler from parent controller - it will be created automatically (only if params.get('toggler')
was called). For first <drop-down>
component - initial state is controller.toggler.isVisible == false
. For second - controller.toggler.isVisible == true
. And HomeController listens to open
event.
- In HomeController we have control over directive.
- Toggler instance incapsulate all logic - open/close methods, events etc.
- Toggler Could be used multiple times.
- Do not need to implement open/close logic inside directitve
Directive Params
class GreetMeController {
constructor(params) {
this.name = params.get('name');
params.watch('name', name => {
// ...
});
}
static render() {
return '<div>{{ controller.name }} </div>'
}
}
valent.component('greet-me', GreetMeController, {
params: {
name: '='
}
});
Available methods:
- get(key) - available only we defined params (component options.params). Returns passed value.
- watch(key, callback) -available only we defined params (component options.params). Create watcher for this key. Callback takes actual param's value.
- parse(key) - available for all passed attributes. Uses
$scope.$parse(attr)
to get value. Do not create extra watchers. Useful for large applications.
If try to get()
or watch()
for keys that are not defined at component's params or not equal component's name (for params with restrict 'A') - will be Exception.
parse()
example:
class GreetMeController {
constructor(params) {
this.name = params.parse('name');
}
static render() {
return '<div>{{ controller.name }} </div>'
}
}
valent.component('greet-me', GreetMeController, {
params: {}
});
Defined structures
import * as primitives from 'valent/utils/primitives';
Use tcomb and define structures. Exported structures.
export const ListNum = list(Num, 'ListNum');
export const ListInt = list(Int, 'ListInt');
export const ListStr = list(Str, 'ListStr');
export const ListBool = list(Bool, 'ListBool');
export const ListDat = list(Dat, 'ListDat');
export const MaybeNum = maybe(Num, 'MaybeNum');
export const MaybeInt = maybe(Int, 'MaybeInt');
export const MaybeStr = maybe(Str, 'MaybeStr');
export const MaybeBool = maybe(Bool, 'MaybeBool');
export const MaybeDat = maybe(Dat, 'MaybeDat');
export const MaybeListNum = maybe(ListNum, 'MaybeListNum');
export const MaybeListInt = maybe(ListInt, 'MaybeListInt');
export const MaybeListStr = maybe(ListStr, 'MaybeListStr');
export const MaybeListBool = maybe(ListBool, 'MaybeListBool');
export const MaybeListDat = maybe(ListDat, 'MaybeListDat');
// [[1,..n],[1..m]...k]
export const MatrixNum = list(ListNum, 'MatrixNum');
// [[1, undefined, ..n],[1 undefined,..m]...k]
export const MatrixMaybeNum = list(MaybeListNum, 'MatrixMaybeNum');
// [['a',..n],['a'..m]...k]
export const MatrixStr = list(ListStr, 'MatrixStr');
// [['a', undefined, .n],['a' undefined,..m]...k]
export const MatrixMaybeStr = list(MaybeListStr, 'MatrixMaybeStr');
export const MatrixDate = list(ListDat, 'MatrixDate');
export const MatrixMaybeDate = list(MaybeListDat, 'MatrixMaybeDate');
export const MatrixBool = list(ListBool, 'MatrixBool');
export const MatrixMaybeBool = list(MaybeListBool, 'MatrixMaybeBool');
Serializers
There 3 serializers
- base serializer
- rename serializer
- url serializer - extends rename serializer with already added rules for all struc that defined in primitives (valent/utils/primitives)
import Serializer from 'valent/serializers/serializer';
import RenameSerializer from 'valent/serializers/rename-serializer';
import UrlSerializer from 'valent/serializers/url-serializer';
Base serializer
Constructor takes 1 argument - params structure. Does NOT contain any encode/decode rules.
- encode(decoded)
- decode(encoded)
Struct example
import primitives from 'valent/utils/primitives';
let struct = {
id: primitives.Num
tags: primitives.MaybeListStr
};
Rename serializer
Extends custom serializer but support attributes renaming encode and returns the, original names during decode. Does NOT contain any encode/decode rules. Constructor takes 1 argument - params structure.
Struct example
import primitives from 'valent/utils/primitives';
let struct = {
// "id" key will encoded into "i"
id: ['i', primitives.Num]
tags: primitives.MaybeListStr
};
Custom serializer
Extends custom serializers from base or rename serializer and add rule for encode/decode.
import tcomb from 'tcomb';
import Serializer from 'valent/serializers/serializer';
let User = tcomb.struct({
id: tcomb.Num,
name: tcomb.Str
});
class UserSerializer extends Serializer {
constructor() {
super({
user: User
});
this.addRule(User, {
encode: (user) => `${user.id}:${user.name}`,
decode: raw => {
let splitted = raw.split(':');
return new UserStruct({
id: splitted[0],
name: splitted[1]
});
}
});
},
/**
* We should override encode/decode methods
* because by default encode method takes object
* same as struct that is defined in constructor
*/
encode(user) {
return super.encode({user});
}
decode(raw) {
let decoded = super.decode(raw);
return decoded.user;
}
}
let serializer = new UserSerializer();
let user = new User({
id: 1,
name: 'Lorem'
});
// encode
let encoded = serializer.encode(user);
equal(encoded, '1:Lorem');
// decode
let decoded = serializer.decode('2:Ipsum');
equal(decided, new User({
id: 2,
name: Ipsum
}));
Url serializer
Extends rename serializer and contain rules for encode/decode. Does not contains URL instance. Rules will work only if struct attributes are references to primitives.
NOTE: Serializers contains WeakMap of encode/decode rules. And keys - are objects from "primitives.js" module. Thats you need to make references to primitives. Primitives works with tcomb - so it also works as type validator.
TODO: map strings to primitive's objects?
{
id: 'number',
tags: 'maybe.listOfStrings'
}
Constructor takes 2 arguments
- struct (same as at rename serializer)
- encode/decode options
Default options:
{
list_delimiter: '~',
matrix_delimiter: '!',
date_format: 'YYYYMMDD'
}
Example:
import primitives from 'valent/utils/primitives';
import UrlSerializer from 'valent/serializers/url-serializer';
let serializer = new UrlSerializer({
id: ['i', primitives.Num]
tags: primitives.MaybeListStr
});
let origin = {
id: 1,
tags: ['a', 'b', 'c']
};
let encoded = serializer.encode(origin);
let decoded = serializer.decode(encoded);
equals(origin, decoded);
Encode rules:
Primitive | Origin | Encoded --------- | ------ | ------- primitives.Num, primitives.MaybeNum | 1 | 1 primitives.Str, primitives.MaybeStr | 'a' | a primitives.Bool, primitives.MaybeBool | true | 1 primitives.Bool, primitives.MaybeBool | false | 0 primitives.Dat, primitives.MaybeDat | new Date() | 2015112 primitives.ListNum, primitives.MaybeListNum | [1, 2, 3] | 1~2~3 primitives.ListMaybeNum | [1, null, 2, 3] | 1~~2,3 primitives.ListStr, primitives.MaybeListStr | ['a', 'b', 'c'] | a~b~c primitives.ListMaybeStr | ['a', null, 'b', 'c'] | a~~b~c primitives.ListBool, primitives.MaybeListBool | [true, false] | 1~0 primitives.ListDat, primitives.MaybeListDat | [new Date(), new Date()] | 2015112~2015112 primitives.MatrixNum, primitives.MaybeMatrixNum | [[1,2],[3,4]] | 1~2!3~4 primitives.MatrixMaybeNum | [[1,2,null,3],[4,null,5]] | 1~2~~3!4~~5 primitives.MatrixStr, primitives.MaybeMatrixStr | [['a','b'],['c','4']] | a~b!c~d primitives.MatrixBool, primitives.MaybeMatrixBool | [[true, false],[false, true]] | 1~0!0~1
Url
If route is defined for controller url instance will be passed to controller's constructor. Also url instance could be created manually.
import Url from 'valent/angular/angular-url';
import * as primitives from 'valent/utils/primitives';
let url = new Url('/store/:id/:tags', {
id: primitives.Num,
tags: primitives.MaybeListStr,
period: primitives.MaybeListDat,
search: ['q', primitives.MaybeListStr
});
Uses URL serializer. Constructor takes 2 arguments:
- pattern - url pattern with placeholders.
- struct - url params structure
import Url from 'valent/angular/angular-url';
import * as primitives from 'valent/utils/primitives';
let url = new Url('/store/:id/:tags', {
id: primitives.Num,
tags: primitives.MaybeListStr,
period: primitives.MaybeListDat,
search: ['q', primitives.MaybeStr
});
let route = url.stringify({
id: 1,
search: 'Hello',
tags: ['yellow', 'large']
});
equal(route, '/store/1/yellow-large?q=Hello');
// And if current url is
// '/store/1/yellow-large?q=Hello?period=20151110-20151120'
let params = url.parse();
equal(parms, {
id: 1,
search: 'Hello',
tags: ['yellow', 'large'],
period: [
// date objects. btw - not sure about correct timezones...
Wed Nov 10 2015 00:00:00 GMT+0200 (EET),
Wed Nov 20 2015 00:00:00 GMT+0200 (EET)
]
});
Structures with Maybe prefix - means that this parameters are not required. If passed parameters have wrong type - will be exception. Parameters that are not described as placeholder at url pattern - will be added as GET parameter.
Provide helpful methods to work with url. Available methods:
go(params, options)
- replace current url with generating according to passed params. Works in angular context - all route events will be fired. options - event options that will be available at url watchers.stringify(params)
- return url according to passed paramsredirect(params)
- same as go() but with page reloadingparse()
- parse current url and return decoded paramswatch(callback)
- listen url changes ($scope event $routeUpdate) and execute callback. Callback arguments - params, diff, options.- params - current url params.
- diff - difference between previous url update.
- options - event options that were passed to go() method
isEmpty()
- return true if there are no params in current urllink(key, fn)
- describe setter for url param.linkTo(store)
- automatically link all structure params to store objectapply()
- execute all added link() functions
Url link and apply example. If url is changed (no matter how - back/forward browser buttons, url.go(params) method, page reload etc.) - each link function will be executed and take current value of binded param.
import Url from 'valent/angular/angular-url';
class HomeController {
filters = {};
constructor(resovled, url) {
/**
* url params "search", "tags"
* will be linked this this.filters object
*/
url.linkTo(this.filters, [
'tags',
'search'
]);
// add link for "id" param
url.link('id', id => {
this.id = id;
});
url.link('search', search => {
this.filters.search = search;
});
url.watch((params, diff, options) => {
/**
* We can not run apply automatically
* on route update because there are
* a lot of cases when developers should
* call apply() manually
*/
url.apply();
});
}
}
valent.controller('store', StoreController, {
url: '/store/:id/:tags',
struct: {
id: primitives.Num,
search: ['q', primitives.MaybeStr,
tags: primitives.MaybeListStr,
}
});
Url Manager
let homeUrl = valent.url.get('home');
homeUrl.go(params);
valent.url.set('custom-url', ManualltCreatedUrlInstance);
- valent.url.get(key) returns url instance for passed key.
- valent.url.set(key, instance) set url instance for key.
For controller with attached url valent.url.set(...)
will be called automatically.
Services
Digest
import digest from 'valent/angular/services/digest';
class HomeController {
constructor() {
digest(this);
}
}
Already debounce digest (trailing = true, timeout = 50). Configurable. Global configuration:
valent.config.set('angular.digest.timeout', 100);
Local configuration:
import digest from 'valent/angular/services/digest';
let configured = digest.configure(100);
Injector
import Injector from 'valent/angular/services/injector';
AngularJS $injector service. Only method get() is available and only after application bootstrap (after angular run phase).
import Injector from 'valent/angular/services/injector';
class HomeController {
constructor() {
let $parse = Injector.get('$parse');
}
}
Watcher
import Watcher from 'valent/angular/services/watcher';
Service is using to create watchers. watchGroup, watchCollection and deep watch - are not available.
NOTE: We highly recommend NOT to use watchers. No matter how watchers are created using this service or native $scope methods.
import Watcher from 'valent/angular/services/watcher';
class HomeController {
title = 'Hello World!';
constructor() {
let watcher = new Watcher(this);
watcher.watch('controller.title', title => {
// ...
});
}
}
new Watcher(context)
- takes one argument - controller's context. Return watcher instance that setuped for controller's $scope.
new Watcher()
- Return watcher instance that setuped for $rootScope.
Events
import Events from 'valent/angular/services/events';
Provide access to $scope events. Constructor takes one argument - controller's context.
import Events from 'valent/angular/services/events';
class HomeController {
constructor() {
let events = new Events(this);
events.on('$routeChangeStart', () => {
// ...
});
events.broadcast('my.custom.event', {
greeting: 'Yo'
});
events.emit('my.custom.event', {
greeting: 'Yo'
});
}
}
Decorators
From PR#10.
TODO: implement and add docs :)
import { Template, Url } from 'valent/decorators';
@Template('home.html');
@Url('/home', '/index');
class HomeController {
// ...
}
Base Components
import BaseScreenController from 'valent/angular/base/screen-controller';
import BaseComponentController from 'valent/angular/base/component-controller';
TODO: implement and add docs :)
Contributing
TODO: add docs :)
TODO
- [ ] Use tcomb for controller/route/component validation!
- [ ] Boilerplate
- [ ] Examples
- [ ] valentjs vs angularjs. Configuration diffs
- [ ] implement TODO application using valent
- [ ] Fix old and add new test
- [ ] redevelop angular-url. Kick extra dependencies (url-pattern)
- [ ] rename directive options - interfaces / optionals / pipes
- [ ] rename DirecitveParams into ComponentParams
- [ ] replace pathes -
/valent/..
. into/valentjs/....
- [ ] redevelop exception system. Right now RuntimeException and RegisterException are not comfortable for debugging.
- [ ] add more useful primitives
- [ ] do not register component's interfaces / options / pipes as angular directive option - scope. This is extra watchers. Use
DirectiveParams.parse()
method to get them.