ng-right
ES7 decorators to make angular 1.x apps awesome. Resembles how angular2 is, but it is not a 1:1, as ng-decorate puts it, This lib is more to handle the angular 1.x DI hell.
Also, this is written in typescript and for sure works with typescript. I have heard it has problems with just es6 and babel. So, use typescript!!!
This is a mash up of mainly ng-decorate and some angular2-now
Readme copy of ng-decorate
Description
ES7 decorators for Angular 1.x. Help you:
- Get around Angular's dependency injection while using ES6 modules.
- Make custom directive declarations very short and semantic.
- Declare bindables directly on class properties.
Perfect for an ES7 / TypeScript application.
This readme assumes you're already using jspm
and System
,
and have an ES6 transpilation workflow with babel
or
typescript
. If not, here's a guide
to get you started: [1].
Contents
Installation
In shell:
npm i --save-dev ng-right
# or
jspm install npm:ng-right
In app:
import {Component, bindTwoWay} from 'ng-right';
@Component({
selector: 'my-accordeon'
})
class X {
@bindTwoWay length: number;
}
Directives
ng-right
provides two directive shortcuts: custom element (@Component
)
and custom attribute (@Attribute
).
@Component
Defines a custom element: a directive with a template and an isolated scope.
Simplest usage:
import {Component} from 'ng-right';
@Component({
selector: 'my-element'
})
class X {}
With default settings, this expands to:
angular.module('myElement', ['ng']).directive('myElement', function() {
return {
restrict: 'E',
scope: {},
templateUrl: 'my-element/my-element.html',
controller: X,
controllerAs: 'self',
bindToController: true
};
});
class X {}
See defaults
for customisation.
@Attribute
Defines a custom attribute.
Simplest usage:
import {Attribute} from 'ng-right';
@Attribute({
selector: '[my-attribute]'
})
class X {}
With default settings, this expands to:
angular.module('myAttribute', ['ng']).directive('myAttribute', function() {
return {
restrict: 'A',
scope: false,
controller: X
};
});
class X {}
See defaults
for customisation.
Directive Options
This applies to both @Component
and @Attribute
.
Any passed options will be included into the resulting
directive definition object,
so you can use the standard directive API on top of ng-right
-specific
options. Example:
@Component({
selector: 'my-element',
scope: {myProperty: '='}
})
selector
: string
Required. This is the selector string for the resulting directive. For
@Attribute
, you can optionally enclose it into brackets:
@Attribute({selector: '[my-attribute]'})
module
: ng.IModule
Optional. The directive will be registered under the given angular module, no new module will be created, and other module options will be ignored:
@Component({
module: angular.module('myApp'),
selector: 'my-element'
})
I recommend using a single angular "module" for the entire application. Angular
dependencies are kinda fake: all providers (services, etc.) are registered
globally in the application injector, and dependencies "bleed through" to sister
modules. Might as well admit this and keep it simple. The real dependency tree
of your application is defined by ES6 modules. See defaults
for
setting up a default angular module.
moduleName
: string
Optional. Dictates the name of the new angular "module" that will be created:
@Attribute({
moduleName: 'app.myAttribute',
selector: '[my-attribute]'
})
If omitted, defaults to the directive's name, as shown above. See
defaults
for how to set up an implicit module.
dependencies
: string[]
Optional. Names of other angular "modules" the newly created module depends on.
This is necessary when you depend on third party services that need to be
dependency-injected (see inject
below):
@Component({
selector: 'my-link',
dependencies: ['ui.router']
})
If omitted, defaults to ['ng']
, as shown above.
inject
: string[]
Deprecated, see @autoinject
.
Optional. Names of angular services that will be dependency-injected and automatically assigned to the class's prototype:
@Component({
selector: 'my-element',
inject: ['$q']
})
class X {
constructor() {
console.log(this.$q)
}
}
This lets you easily get hold of angular services while using ES6 modules for everything else. The magic happens during Angular's "run phase", before any directives are instantiated.
See the gotcha
for the possible dependency injection issues. They
can be easily avoided by using just one module.
injectStatic
: string[]
Deprecated, see @autoinject
.
Optional. Works exactly like inject
, but assigns the injected services to the
class as static properties.
@Component({
selector: 'my-element',
injectStatic: ['$http']
})
class X {
constructor() {
console.log(X.$http)
}
}
Statics
These directive options can be included as static methods or properties:
template
templateUrl
compile
link
scope
Example:
@Attribute({
selector: 'svg-icon'
})
class X {
@autoinject $templateCache;
static template($element) {
var element = $element[0];
var src = 'svg/' + element.getAttribute('icon') + '.svg';
return X.$templateCache.get(src);
}
}
Services
@Ambient
Dependency injection helper. It's a strict subset of other decorators in this library. Use it when you want to obtain Angular's services without creating any new directives or services.
import {Ambient, autoinject} from 'ng-right';
@Ambient
class X {
@autoinject $q;
@autoinject static $http;
constructor() {
console.log(this.$q)
console.log(X.$http)
}
}
(See @autoinject
below.)
Just like with other class decorators, you can include module
, moduleName
and so on. For this particular decorator, arguments are optional.
@Service
Same as @Ambient
but registers a new angular service with the given name. The
serviceName
option is mandatory. This is useful when you're porting a legacy
application, where some parts still rely on Angular's DI.
Create a service:
@Service({
serviceName: 'MySpecialClass'
})
class X {}
Get it in Angular's DI:
angular.module('app').run(['MySpecialClass', function(MySpecialClass) {
/* ... */
}]);
@Controller
Same as @Ambient
but also registers the class as an "old school" controller
under the given name. Can optionally publish the class to the DI system, just
like @Service
.
Register the controller:
@Controller({
controllerName: 'X'
})
class X {}
@Controller({
controllerName: 'Y',
serviceName: 'Y'
})
class Y {}
Use in a template:
<div ng-controller="X"></div>
This type of controller usage is outdated and generally not recommended. Use
@Component
instead. This decorator is provided to ease migration of legacy
apps to ES6/7.
Service Options
This applies to @Ambient
, @Service
, and @Controller
.
module
: ng.IModule
moduleName
: string[]
dependencies
: string[]
See Directive Options.
Bindings
Directly annotate class properties to declare them as bindable. Perfect with
TypeScript. When used with @Component
or @Attribute
, the annotations are
grouped and converted into a scope: {/* ... */}
declaration for the internal
directive definition object.
Example:
import {Component, bindString, bindTwoWay} from 'ng-right';
@Component({
selector: 'editable'
})
class X {
@bindString label: string;
@bindTwoWay value: string;
}
Expands to:
import {Component} from 'ng-right';
@Component({
selector: 'editable',
scope: {
label: '@',
value: '='
}
})
class X {
label: string;
value: string;
}
This lets you declare bindings directly in the class body and makes them more semantic.
@bindString
@Component({
selector: 'my-element'
})
class X {
@bindString first: string;
@bindString('last') second: string;
}
Expands to:
@Component({
selector: 'my-element',
scope: {
first: '@',
second: '@last'
}
})
class X {
first: string;
second: string;
}
@bindTwoWay
@Component({
selector: 'my-element'
})
class X {
@bindTwoWay first: any;
@bindTwoWay({collection: true, optional: true, key: 'last'})
second: any;
}
Expands to:
@Component({
selector: 'my-element',
scope: {
first: '=',
second: '=*?last'
}
})
class X {
first: any;
second: any;
}
@bindOneWay
This is a special feature of ng-right
. It bridges the gap between Angular
2, where one-way binding is the default, and Angular 1.x, which doesn't support
it natively.
Example with a hardcoded string:
<controlled-input value="'constant value'"></controlled-input>
@Component({
selector: 'controlled-input'
})
class X {
@bindOneWay value: any;
constructor() {
this.value = 123; // has no effect
console.log(this.value); // prints 'constant value'
}
}
@bindExpression
@Component({
selector: 'my-element'
})
class X {
@bindExpression first: Function;
@bindExpression('last') second: Function;
}
Expands to:
@Component({
selector: 'my-element',
scope: {
first: '&',
second: '&last'
}
})
class X {
first: Function;
second: Function;
}
@autoinject
Properties annotated with @autoinject
are automatically assigned to the prototype
(instance properties) or class (static properties). You don't need to inject them
in the constructor.
Must be used with one of the class decorators, like @Component
or @Ambient
.
import {Component, autoinject} from 'ng-right';
@Component({
selector: 'todo-list'
})
class X {
@autoinject $q;
@autoinject static $timeout;
constructor() {
console.log(this.$q);
console.log(X.$timeout);
}
}
Works great with TypeScript and property type annotations.
As an alternative, you can pass arrays of properties as inject
and
injectStatic
to the class decorator (deprecated).
Note that this only works for global services. For contextual dependencies
like $scope
, use constructor injection:
class X {
static $inject = ['$scope', '$element'];
constructor($scope, $element) {
/* ... */
}
}
With TypeScript, use the private
or public
modifier to automatically assign
the injected value to the instance:
class X {
static $inject = ['$scope', '$element'];
constructor(private $scope, private $element) {
/* ... */
}
}
Extras based off of angular2-now
@State
import {State, autoinject} from 'ng-right';
@State({
name: 'name-of-state',
templateUrl: 'something.tpl.html'
})
class StateController {
@autoinject $q;
constructor() {
console.log(this.$q);
}
}
@View
- This decorator is optional and included to be more like angular2
- Can be used with Component to specify template, templateUrl
import {Component, View, autoinject} from 'ng-right';
@Component({
selector: 'app-head'
})
@View({
template: '<div> the app head </div>',
templateUrl: 'path-to-template.tpl.html' // template or templateUrl
})
class appHead {
@autoinject $q;
constructor() {
console.log(this.$q);
}
}