├── .gitignore ├── api ├── images │ ├── cat.png │ ├── cheese.png │ ├── durian.png │ ├── jumper.png │ ├── bearing.png │ ├── cat-thumb.png │ ├── football.png │ ├── toaster.png │ ├── tapemeasure.png │ ├── bearing-thumb.png │ ├── cheese-thumb.png │ ├── durian-thumb.png │ ├── football-thumb.png │ ├── jumper-thumb.png │ ├── toaster-thumb.png │ └── tapemeasure-thumb.png └── items.json ├── src ├── app │ ├── directives │ │ ├── item.tpl.html │ │ ├── itemDisplay.js │ │ ├── itemSelector.js │ │ ├── selectable.js │ │ ├── stateDisplay.ts │ │ └── item.js │ ├── app.js │ ├── controllers │ │ └── appController.js │ ├── services │ │ ├── thingFactory.js │ │ ├── itemsService.js │ │ └── statesProvider.js │ └── utils │ │ └── register.js ├── index.html └── styles │ └── main.less ├── .gitattributes ├── bower.json ├── package.json ├── gulpfile.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | bower_components 3 | node_modules 4 | assets-source -------------------------------------------------------------------------------- /api/images/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/cat.png -------------------------------------------------------------------------------- /src/app/directives/item.tpl.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /api/images/cheese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/cheese.png -------------------------------------------------------------------------------- /api/images/durian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/durian.png -------------------------------------------------------------------------------- /api/images/jumper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/jumper.png -------------------------------------------------------------------------------- /api/images/bearing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/bearing.png -------------------------------------------------------------------------------- /api/images/cat-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/cat-thumb.png -------------------------------------------------------------------------------- /api/images/football.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/football.png -------------------------------------------------------------------------------- /api/images/toaster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/toaster.png -------------------------------------------------------------------------------- /api/images/tapemeasure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/tapemeasure.png -------------------------------------------------------------------------------- /api/images/bearing-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/bearing-thumb.png -------------------------------------------------------------------------------- /api/images/cheese-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/cheese-thumb.png -------------------------------------------------------------------------------- /api/images/durian-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/durian-thumb.png -------------------------------------------------------------------------------- /api/images/football-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/football-thumb.png -------------------------------------------------------------------------------- /api/images/jumper-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/jumper-thumb.png -------------------------------------------------------------------------------- /api/images/toaster-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/toaster-thumb.png -------------------------------------------------------------------------------- /api/images/tapemeasure-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkh44/angular-es6/master/api/images/tapemeasure-thumb.png -------------------------------------------------------------------------------- /src/app/app.js: -------------------------------------------------------------------------------- 1 | 2 | var app = angular.module('app', []); 3 | 4 | app.constant('config', { 5 | apiUrl: '../api/' 6 | }); 7 | 8 | 9 | app.config((statesProvider) => { 10 | statesProvider.setPrefix('You are feeling'); 11 | }); 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-es6", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/michaelbromley/angular-es6", 5 | "authors": [ 6 | "Michael Bromley " 7 | ], 8 | "moduleType": [ 9 | "globals" 10 | ], 11 | "keywords": [ 12 | "angular", 13 | "es6" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test", 21 | "tests" 22 | ], 23 | "dependencies": { 24 | "angular": "~1.3.9", 25 | "requirejs": "~2.1.15" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/controllers/appController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The one and only controller used in this app. 3 | */ 4 | class AppController { 5 | 6 | /*@ngInject*/ 7 | constructor($scope, itemsService, thingFactory) { 8 | this.items = []; 9 | this.selection = []; 10 | 11 | itemsService.getItems().then( result => this.items = result ); 12 | 13 | $scope.$watch('vm.items', () => { 14 | this.selection = this.items.filter(item => item.selected); 15 | }, true); 16 | 17 | this.makeThing = () => { thingFactory.newThing() }; 18 | 19 | 20 | this.$inject = ['$scope', 'itemService', 'Thing']; 21 | } 22 | 23 | } 24 | 25 | register('app').controller('AppController', AppController); -------------------------------------------------------------------------------- /src/app/directives/itemDisplay.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example of a "component" directive which encapsulates a template. 3 | */ 4 | class ItemDisplayDirective { 5 | 6 | constructor() { 7 | this.template = '
'; 8 | this.restrict = 'E'; 9 | this.replace = true; 10 | this.scope = { 11 | collection: '=', 12 | start: '=' 13 | } 14 | } 15 | 16 | link(scope) { 17 | 18 | scope.$watch('start', (value) => { 19 | if (value) { 20 | scope.items = scope.collection; 21 | } 22 | }); 23 | } 24 | } 25 | 26 | register('app').directive('itemDisplay', ItemDisplayDirective); -------------------------------------------------------------------------------- /src/app/services/thingFactory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A thing is an object which will be instantiated and returned by the ThingFactory 3 | */ 4 | class Thing { 5 | 6 | constructor() { 7 | var time = new Date().getTime(); 8 | console.log(`Created a new Thing at ${time}!`); 9 | 10 | this.explode(); 11 | } 12 | 13 | explode() { 14 | console.log('BOOM!'); 15 | } 16 | 17 | } 18 | 19 | /** 20 | * The ThingFactory class creates new things 21 | */ 22 | class ThingFactory { 23 | 24 | /*@ngInject*/ 25 | constructor($timeout) { 26 | this.$timeout = $timeout; 27 | } 28 | 29 | newThing() { 30 | console.log('Getting a new Thing...'); 31 | return this.$timeout(() => new Thing(), 100); 32 | } 33 | } 34 | 35 | register('app').factory('thingFactory', ThingFactory); -------------------------------------------------------------------------------- /src/app/services/itemsService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides access to the JSON endpoint which contains the data about the items. 3 | */ 4 | class ItemsService { 5 | 6 | /*@ngInject*/ 7 | constructor($http, config) { 8 | this.$http = $http; 9 | this.config = config; 10 | } 11 | 12 | getItems() { 13 | var apiUrl = this.config.apiUrl; 14 | 15 | return this.$http 16 | .get(apiUrl + 'items') 17 | .then((result) => { 18 | // prepend the API url to the images 19 | return result.data.map((item) => { 20 | 21 | item.image = apiUrl + item.image; 22 | item.thumb = apiUrl + item.thumb; 23 | return item; 24 | 25 | }); 26 | }); 27 | } 28 | 29 | } 30 | 31 | register('app').service('itemsService', ItemsService); 32 | -------------------------------------------------------------------------------- /src/app/directives/itemSelector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example of a "component" directive which encapsulates a template. 3 | */ 4 | class ItemSelectorDirective { 5 | 6 | /*@ngInject*/ 7 | constructor($timeout) { 8 | this.template = '
Which ones please you the most?
'; 13 | this.restrict = 'E'; 14 | this.replace = true; 15 | this.scope = { 16 | collection: '=' 17 | }; 18 | this.$timeout = $timeout; 19 | } 20 | } 21 | 22 | register('app').directive('itemSelector', ItemSelectorDirective); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-es6", 3 | "version": "1.0.0", 4 | "description": "An experiment in using ES6 classes with AngularJS", 5 | "main": "gulpfile.js", 6 | "dependencies": { 7 | "event-stream": "^3.2.1", 8 | "gulp": "^3.8.10", 9 | "gulp-less": "^2.0.1", 10 | "gulp-inject": "^1.1.1", 11 | "gulp-6to5": "^2.0.2", 12 | "gulp-ng-annotate": "^0.5.0", 13 | "gulp-typescript": "^2.4.1" 14 | }, 15 | "devDependencies": {}, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/michaelbromley/angular-es6.git" 22 | }, 23 | "keywords": [ 24 | "angularjs", 25 | "es6" 26 | ], 27 | "author": "Michael Bromley ", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/michaelbromley/angular-es6/issues" 31 | }, 32 | "homepage": "https://github.com/michaelbromley/angular-es6" 33 | } 34 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AngularJS & ES6 Demo: A Pleasing Array of Items 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

A Pleasing Array of Items

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /api/items.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name" : "Cheese", 4 | "image" : "images/cheese.png", 5 | "thumb" : "images/cheese-thumb.png" 6 | }, 7 | { 8 | "name" : "Jumpers", 9 | "image" : "images/jumper.png", 10 | "thumb" : "images/jumper-thumb.png" 11 | }, 12 | { 13 | "name" : "Cats", 14 | "image" : "images/cat.png", 15 | "thumb" : "images/cat-thumb.png" 16 | }, 17 | { 18 | "name" : "Toasters", 19 | "image" : "images/toaster.png", 20 | "thumb" : "images/toaster-thumb.png" 21 | }, 22 | { 23 | "name" : "Durians", 24 | "image" : "images/durian.png", 25 | "thumb" : "images/durian-thumb.png" 26 | }, 27 | { 28 | "name" : "Tape Measures", 29 | "image" : "images/tapemeasure.png", 30 | "thumb" : "images/tapemeasure-thumb.png" 31 | }, 32 | { 33 | "name" : "Footballs", 34 | "image" : "images/football.png", 35 | "thumb" : "images/football-thumb.png" 36 | }, 37 | { 38 | "name" : "Bearings", 39 | "image" : "images/bearing.png", 40 | "thumb" : "images/bearing-thumb.png" 41 | } 42 | ] -------------------------------------------------------------------------------- /src/app/directives/selectable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example of a "behavioural" directive that has no template, but decorates the behaviour of the 3 | * element which it is attached to. 4 | * 5 | * The directive makes an element "selectable", which means when it is clicked, the class `selected` will be added to the element, and if a 6 | * object is supplied as the value of the "selectable" attribute, the directive will attempt to add a property `selected = true` to that 7 | * object. 8 | */ 9 | class Selectable { 10 | 11 | constructor() { 12 | this.restrict = 'A'; 13 | } 14 | 15 | compile(tElement) { 16 | tElement.addClass('selectable'); 17 | } 18 | 19 | link(scope, element, attrs) { 20 | var model = scope.$eval(attrs.selectable) || {}; 21 | 22 | element.on('click', () => { 23 | scope.$apply(() => { 24 | element.toggleClass('selected'); 25 | model.selected = !model.selected; 26 | }); 27 | }); 28 | } 29 | 30 | } 31 | 32 | register('app').directive('selectable', Selectable); -------------------------------------------------------------------------------- /src/app/directives/stateDisplay.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A directive class written in TypeScript. 3 | */ 4 | class StateDisplayDirective { 5 | 6 | private template; 7 | private restrict; 8 | private replace; 9 | private scope; 10 | private $interval; 11 | private states; 12 | 13 | /*@ngInject*/ 14 | constructor($interval, states) { 15 | this.template = '
{{ prefix }} {{ currentState }}
'; 16 | this.restrict = 'E'; 17 | this.replace = true; 18 | this.scope = { 19 | start: '=' 20 | }; 21 | 22 | this.$interval = $interval; 23 | this.states = states; 24 | } 25 | 26 | link(scope) { 27 | 28 | scope.prefix = this.states.getPrefix(); 29 | scope.currentState = this.states.getNextState(); 30 | 31 | scope.$watch('start', (val) => { 32 | if (val) { 33 | this.$interval(() => scope.currentState = this.states.getNextState(), 3500); 34 | } 35 | }); 36 | 37 | } 38 | } 39 | 40 | 41 | // Declare the register function in order to avoid TypeScript compiler errors. 42 | declare var register : any; 43 | 44 | register('app').directive('stateDisplay', StateDisplayDirective); -------------------------------------------------------------------------------- /src/styles/main.less: -------------------------------------------------------------------------------- 1 | html { 2 | overflow: hidden; 3 | font-family: "Helvetic Neue", Helvetica, Arial, sans-serif; 4 | text-align: center; 5 | } 6 | 7 | .items-selector { 8 | 9 | width: 650px; 10 | margin: auto; 11 | 12 | ul li { 13 | list-style-type: none; 14 | display: inline-block; 15 | width: 120px; 16 | text-align: center; 17 | border: 1px solid #eee; 18 | margin: 5px; 19 | padding: 5px; 20 | transition: border-color 0.3s; 21 | font-size: 14px; 22 | 23 | &:hover { 24 | border-color: #999; 25 | } 26 | 27 | img { 28 | display: block; 29 | margin: auto; 30 | } 31 | } 32 | } 33 | 34 | .selectable { 35 | cursor: pointer; 36 | 37 | &.selected { 38 | font-weight: bold; 39 | border-color: red; 40 | } 41 | } 42 | 43 | button.show-me { 44 | font-size: 24px; 45 | padding: 10px; 46 | border-radius: 8px; 47 | } 48 | 49 | .item { 50 | transition: all 1s; 51 | } 52 | 53 | button.thing-button { 54 | position: absolute; 55 | bottom: 30px; 56 | left: 30px; 57 | border-radius: 3px; 58 | z-index: 50; 59 | } 60 | 61 | .footer { 62 | position: absolute; 63 | bottom: 30px; 64 | display: block; 65 | width: 100%; 66 | } 67 | 68 | .state-label { 69 | position: relative; 70 | z-index: 10; 71 | font-size: 64px; 72 | color: darkgreen; 73 | background-color: rgba(255,255,255,0.7); 74 | } -------------------------------------------------------------------------------- /src/app/directives/item.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A fairly fully-featured directive, including an external template file, dependencies, compile & link, isolate scope. 3 | */ 4 | class ItemDirective { 5 | 6 | /*@ngInject*/ 7 | constructor($interval, $timeout) { 8 | this.templateUrl = 'app/directives/item.tpl.html'; 9 | this.restrict = 'E'; 10 | this.replace = true; 11 | this.scope = { 12 | model: '=' 13 | }; 14 | 15 | this.$interval = $interval; 16 | this.$timeout = $timeout; 17 | } 18 | 19 | compile(tElement) { 20 | 21 | tElement.css('position', 'absolute'); 22 | tElement.css('opacity', '0'); 23 | tElement.css('left', (window.innerWidth / 2 - 150) + 'px'); 24 | tElement.css('top', (window.innerHeight / 2 - 150) + 'px'); 25 | 26 | } 27 | 28 | link(scope, element) { 29 | 30 | var interval = Math.random() * 500 + 800; 31 | this.$timeout(() => element.css('opacity', '1'), 500); 32 | this.$interval(() => this.move(element), interval); 33 | 34 | } 35 | 36 | move(element) { 37 | var newPos = this.getNewPosition(); 38 | element.css('left', (newPos.x - 150) + 'px'); 39 | element.css('top', (newPos.y - 150) + 'px'); 40 | } 41 | 42 | getNewPosition() { 43 | var width = window.innerWidth, 44 | height = window.innerHeight; 45 | 46 | return { 47 | x: Math.random() * width, 48 | y: Math.random() * height 49 | }; 50 | } 51 | } 52 | 53 | register('app').directive('item', ItemDirective); -------------------------------------------------------------------------------- /src/app/services/statesProvider.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example of a provider which can be configured at runtime in a `config()` block with the `setPrefix()` method. 3 | */ 4 | class StatesProvider { 5 | 6 | constructor() { 7 | this.prefix = "You are"; 8 | this.states = [ 9 | 'okay', 10 | 'not too bad', 11 | 'contented', 12 | 'quite satisfied', 13 | 'moderately gratified', 14 | 'well chuffed', 15 | 'highly pleased', 16 | 'highly pleased indeed' 17 | ]; 18 | } 19 | 20 | /** 21 | * This method allows the prefix value to be configured at runtime. 22 | * @param value 23 | */ 24 | setPrefix(value) { 25 | this.prefix = value; 26 | } 27 | 28 | /** 29 | * The `$get` method is a requirement of the Angular provider registration API, and is a factory function that 30 | * returns our service object. 31 | * 32 | * @returns {{getNextState: Function, getPrefix: Function}} 33 | */ 34 | /*@ngInject*/ 35 | $get($timeout) { 36 | var index = 0; 37 | $timeout(() => console.log('This is the statesProvider $get method being invoked.'), 100); 38 | 39 | return { 40 | getNextState: () => { 41 | var currentState; 42 | 43 | if (index < this.states.length) { 44 | currentState = this.states[index]; 45 | } else { 46 | currentState = this.states[this.states.length - 1]; 47 | } 48 | 49 | index ++; 50 | return currentState; 51 | }, 52 | getPrefix: () => this.prefix 53 | }; 54 | } 55 | } 56 | 57 | register('app').provider('states', StatesProvider); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var inject = require('gulp-inject'); 3 | var to5 = require('gulp-6to5'); 4 | var annotate = require('gulp-ng-annotate'); 5 | var less = require('gulp-less'); 6 | var ts = require('gulp-typescript'); 7 | var es = require('event-stream'); 8 | 9 | gulp.task('scripts', function () { 10 | var javascripts = gulp.src('./src/**/*.js') 11 | .pipe(to5()); 12 | 13 | var typescripts = gulp.src('./src/**/*.ts') 14 | .pipe(ts({ 15 | target: 'ES5' 16 | })); 17 | 18 | es.merge(typescripts.js, javascripts) 19 | .pipe(annotate()) 20 | .pipe(gulp.dest('./build')); 21 | }); 22 | 23 | gulp.task('styles', function() { 24 | return gulp.src('./src/**/*.less') 25 | .pipe(less()) 26 | .pipe(gulp.dest('./build')); 27 | }); 28 | 29 | gulp.task('templates', function() { 30 | return gulp.src('./src/**/*.tpl.html') 31 | .pipe(gulp.dest('./build')); 32 | }); 33 | 34 | gulp.task('index', ['scripts', 'styles', 'templates'], function() { 35 | 36 | var target = gulp.src('./src/index.html'); 37 | 38 | var js = gulp.src([ 39 | '../bower_components/angular/angular.js', 40 | 'app/utils/register.js', 41 | 'app/app.js', 42 | '**/!(app.js)' 43 | ], {read: false, cwd: './build/'}); 44 | 45 | var css = gulp.src([ 46 | 'styles/main.css' 47 | ], {read: false, cwd: './build/'}); 48 | 49 | target 50 | .pipe(inject(js, { addRootSlash: false })) 51 | .pipe(inject(css, { addRootSlash: false })) 52 | .pipe(gulp.dest('./build')); 53 | }); 54 | 55 | gulp.task('watch', ['index'], function () { 56 | 57 | gulp.watch(['./src/**/*.js', './src/**/*.ts'], ['scripts']); 58 | gulp.watch('./src/**/*.less', ['styles']); 59 | gulp.watch('./src/**/*.tpl.html', ['templates']); 60 | gulp.watch('./src/index.html', ['index']); 61 | 62 | }); 63 | 64 | 65 | gulp.task('default', ['index']); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular ES6 2 | 3 | An example approach to using ES6 classes in an AngularJS 1.x app. 4 | 5 | **Please see the article [Exploring ES6 Classes In AngularJS 1.x](http://www.michaelbromley.co.uk/blog/350/exploring-es6-classes-in-angularjs-1-x) for 6 | a full explanation.** 7 | 8 | **Working demo [here](http://www.michaelbromley.co.uk/experiments/angular-es6-demo/build/)** 9 | 10 | ## register.js 11 | 12 | The style of class definition you see below is enabled by including the file [register.js](src/app/utils/register.js) in the project, which exposes the global function `register` 13 | 14 | The API is as follows: 15 | 16 | ```JavaScript 17 | class MyAngularComponent { 18 | // ... 19 | } 20 | 21 | register(appName) 22 | .controller('MyController', MyAngularComponent) 23 | .service('myService', MyAngularComponent) 24 | .provider('myOtherService', MyAngularComponent) 25 | .factory('myFactory', MyAngularComponent) 26 | .directive('myDirective', MyAngularComponent); 27 | ``` 28 | 29 | ## Example Component Classes 30 | 31 | ### Service 32 | 33 | ```JavaScript 34 | class UserService { 35 | /*@ngInject*/ 36 | constructor($http) { 37 | this.$http = $http; 38 | } 39 | getFullName() { 40 | return this.$http.get('api/user/details'); 41 | } 42 | } 43 | 44 | register('app').service('userService', UserService); 45 | ``` 46 | 47 | ### Controller 48 | 49 | ```JavaScript 50 | class MyController { 51 | /*@ngInject*/ 52 | constructor(userService) { 53 | userService.getFullName() 54 | .then(result => this.userName = result.fullName); 55 | } 56 | } 57 | 58 | register('app').controller('MyController', MyController); 59 | ``` 60 | 61 | ### Factory 62 | 63 | ```JavaScript 64 | class ThingFactory { 65 | /*@ngInject*/ 66 | constructor($timeout) { 67 | this.$timeout = $timeout; 68 | } 69 | newThing() { 70 | console.log('Getting a new Thing...'); 71 | return this.$timeout(() => new Thing(), 1000); 72 | } 73 | } 74 | 75 | register('app').factory('thingFactory', ThingFactory); 76 | ``` 77 | 78 | ### Directive 79 | 80 | ```JavaScript 81 | class MyDirective { 82 | /*@ngInject*/ 83 | constructor($interval) { 84 | this.template = '
I\'m a directive!
'; 85 | this.restrict = 'E'; 86 | this.scope = {} 87 | // etc. for the usual config options 88 | 89 | // allows us to use the injected dependencies 90 | // elsewhere in the directive (e.g. compile or link function) 91 | this.$interval = $interval; 92 | } 93 | 94 | // optional compile function 95 | compile(tElement) { 96 | tElement.css('position', 'absolute'); 97 | } 98 | 99 | // optional link function 100 | link(scope, element) { 101 | this.$interval(() => this.move(element), 1000); 102 | } 103 | 104 | move(element) { 105 | element.css('left', (Math.random() * 500) + 'px'); 106 | element.css('top', (Math.random() * 500) + 'px'); 107 | } 108 | } 109 | 110 | register('app').directive('myDirective', MyDirective); 111 | ``` 112 | 113 | ### Provider 114 | 115 | ```JavaScript 116 | class ThingServiceProvider { 117 | constructor() { 118 | this.apiPath = 'default/api'; 119 | } 120 | setApiPath(value) { 121 | this.apiPath = value; 122 | } 123 | /*@ngInject*/ 124 | $get($http) { 125 | return { 126 | getThings: () => $http.get(this.apiPath) 127 | }; 128 | } 129 | } 130 | 131 | register('app').provider('thingService', ThingServiceProvider); 132 | ``` 133 | 134 | ## Build 135 | 136 | Clone this repo and then `npm install` and `bower install` to download the required dependencies. 137 | 138 | Then `gulp watch` and start hacking! 139 | 140 | ## License 141 | 142 | MIT 143 | -------------------------------------------------------------------------------- /src/app/utils/register.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A helper class to simplify registering Angular components and provide a consistent syntax for doing so. 3 | */ 4 | function register(appName) { 5 | 6 | var app = angular.module(appName); 7 | 8 | return { 9 | directive: directive, 10 | controller: controller, 11 | service: service, 12 | provider: provider, 13 | factory: factory 14 | }; 15 | 16 | function directive(name, constructorFn) { 17 | 18 | constructorFn = _normalizeConstructor(constructorFn); 19 | 20 | if (!constructorFn.prototype.compile) { 21 | // create an empty compile function if none was defined. 22 | constructorFn.prototype.compile = () => {}; 23 | } 24 | 25 | var originalCompileFn = _cloneFunction(constructorFn.prototype.compile); 26 | 27 | // Decorate the compile method to automatically return the link method (if it exists) 28 | // and bind it to the context of the constructor (so `this` works correctly). 29 | // This gets around the problem of a non-lexical "this" which occurs when the directive class itself 30 | // returns `this.link` from within the compile function. 31 | _override(constructorFn.prototype, 'compile', function () { 32 | return function () { 33 | originalCompileFn.apply(this, arguments); 34 | 35 | if (constructorFn.prototype.link) { 36 | return constructorFn.prototype.link.bind(this); 37 | } 38 | }; 39 | }); 40 | 41 | var factoryArray = _createFactoryArray(constructorFn); 42 | 43 | app.directive(name, factoryArray); 44 | return this; 45 | } 46 | 47 | function controller(name, contructorFn) { 48 | app.controller(name, contructorFn); 49 | return this; 50 | } 51 | 52 | function service(name, contructorFn) { 53 | app.service(name, contructorFn); 54 | return this; 55 | } 56 | 57 | function provider(name, constructorFn) { 58 | app.provider(name, constructorFn); 59 | return this; 60 | } 61 | 62 | function factory(name, constructorFn) { 63 | constructorFn = _normalizeConstructor(constructorFn); 64 | var factoryArray = _createFactoryArray(constructorFn); 65 | app.factory(name, factoryArray); 66 | return this; 67 | } 68 | 69 | /** 70 | * If the constructorFn is an array of type ['dep1', 'dep2', ..., constructor() {}] 71 | * we need to pull out the array of dependencies and add it as an $inject property of the 72 | * actual constructor function. 73 | * @param input 74 | * @returns {*} 75 | * @private 76 | */ 77 | function _normalizeConstructor(input) { 78 | var constructorFn; 79 | 80 | if (input.constructor === Array) { 81 | // 82 | var injected = input.slice(0, input.length - 1); 83 | constructorFn = input[input.length - 1]; 84 | constructorFn.$inject = injected; 85 | } else { 86 | constructorFn = input; 87 | } 88 | 89 | return constructorFn; 90 | } 91 | 92 | /** 93 | * Convert a constructor function into a factory function which returns a new instance of that 94 | * constructor, with the correct dependencies automatically injected as arguments. 95 | * 96 | * In order to inject the dependencies, they must be attached to the constructor function with the 97 | * `$inject` property annotation. 98 | * 99 | * @param constructorFn 100 | * @returns {Array.} 101 | * @private 102 | */ 103 | function _createFactoryArray(constructorFn) { 104 | // get the array of dependencies that are needed by this component (as contained in the `$inject` array) 105 | var args = constructorFn.$inject || []; 106 | var factoryArray = args.slice(); // create a copy of the array 107 | // The factoryArray uses Angular's array notation whereby each element of the array is the name of a 108 | // dependency, and the final item is the factory function itself. 109 | factoryArray.push((...args) => { 110 | //return new constructorFn(...args); 111 | var instance = new constructorFn(...args); 112 | for (var key in instance) { 113 | instance[key] = instance[key]; 114 | } 115 | return instance; 116 | }); 117 | 118 | return factoryArray; 119 | } 120 | 121 | /** 122 | * Clone a function 123 | * @param original 124 | * @returns {Function} 125 | */ 126 | function _cloneFunction(original) { 127 | return function() { 128 | return original.apply(this, arguments); 129 | }; 130 | } 131 | 132 | /** 133 | * Override an object's method with a new one specified by `callback`. 134 | * @param object 135 | * @param methodName 136 | * @param callback 137 | */ 138 | function _override(object, methodName, callback) { 139 | object[methodName] = callback(object[methodName]) 140 | } 141 | 142 | } --------------------------------------------------------------------------------