├── .npm └── package │ ├── .gitignore │ ├── npm-shrinkwrap.json │ └── README ├── .gitignore ├── webpack ├── dist.js ├── index.js ├── prod.js ├── test.js └── common.js ├── src ├── api │ ├── local-injectables.js │ ├── scope-new.js │ ├── meteor-reactive.js │ ├── controller.js │ ├── scope-shared.js │ ├── service.js │ ├── filter.js │ ├── set-module.js │ ├── view.js │ ├── options.js │ ├── bootstrap.js │ ├── inject.js │ ├── meteor-method.js │ ├── state.js │ └── component.js ├── common.js ├── exports.js ├── utils.js └── angular2-now.js ├── .eslintignore ├── typings.json ├── webpack.config.js ├── .npmignore ├── tests ├── directive-spec.js ├── basic-spec.js ├── local-injectables-spec.js ├── scope-new-spec.js ├── scope-shared-spec.js ├── index-spec.js ├── meteor-reactive-spec.js ├── options-spec.js ├── set-module-spec.js ├── service-spec.js ├── controller-spec.js ├── view-spec.js ├── bootstrap-spec.js ├── filter-spec.js ├── inject-spec.js ├── meteor-method-spec.js ├── state-spec.js └── component-spec.js ├── typings └── angular2-now │ ├── typings.json │ └── index.d.ts ├── .editorconfig ├── .travis.yml ├── package.js ├── bower.json ├── .bower.json ├── .eslintrc ├── LICENSE ├── gulpfile.js ├── CHANGELOG.md ├── karma.config.js ├── package.json ├── dist ├── angular2-now.min.js └── angular2-now.js └── README.md /.npm/package/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | coverage 4 | npm-debug.log* 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /webpack/dist.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./common')({ 2 | output: { 3 | filename: '[name].js' 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /.npm/package/npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "angular2-now": { 4 | "version": "1.1.6" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/api/local-injectables.js: -------------------------------------------------------------------------------- 1 | export function LocalInjectables(target) { 2 | target.localInjectables = true; 3 | return target; 4 | } 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/exports.js 2 | webpack 3 | node_modules 4 | coverage 5 | dist 6 | gulpfile.js 7 | package.js 8 | karma.config.js 9 | webpack.config.js 10 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "angular2-now": "github:onderceylan/angular2-now/angular2-now.d.ts#e22d64ef073c7a460ca75225090b42371fde002c" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | require('argv-set-env')(); 2 | var webpack = require('./webpack')(process.env.NODE_ENV === 'production' ? 'prod' : 'dist'); 3 | 4 | module.exports = webpack; 5 | -------------------------------------------------------------------------------- /src/api/scope-new.js: -------------------------------------------------------------------------------- 1 | // Requests a new scope to be created when the directive is created. 2 | // The other way to do this is to pass "scope: true" to @Component. 3 | export function ScopeNew(target) { 4 | target.scope = true; 5 | return target; 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage 2 | webpack 3 | tests 4 | src 5 | .npm 6 | .editorconfig 7 | .eslintignore 8 | .eslintrc 9 | .travis.yml 10 | .bower.json 11 | bower.json 12 | gulpfile.js 13 | karma.config.js 14 | package.js 15 | webpack.config.js 16 | .idea 17 | -------------------------------------------------------------------------------- /tests/directive-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('@Directive', () => { 3 | it('should be an alias for @Component()', () => { 4 | expect(angular2now.Directive).toBe(angular2now.Component); 5 | }); 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /webpack/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(type) { 2 | const types = ['dist', 'prod', 'test']; 3 | 4 | if (types.indexOf(type) === -1) { 5 | throw new Error('Unknown webpack configuration'); 6 | } 7 | 8 | return require('./' + type); 9 | }; 10 | -------------------------------------------------------------------------------- /webpack/prod.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | module.exports = require('./common')({ 4 | output: { 5 | filename: '[name].min.js' 6 | }, 7 | devtool: 'source-map', 8 | plugins: [ 9 | new webpack.optimize.UglifyJsPlugin() 10 | ] 11 | }); 12 | -------------------------------------------------------------------------------- /src/api/meteor-reactive.js: -------------------------------------------------------------------------------- 1 | // Turn on an indication to run $reactive(this).attach($scope) for the component's controller. 2 | // Uses with Angular-Meteor: http://angular-meteor.com, v1.3 and up only 3 | export function MeteorReactive(target) { 4 | target.meteorReactive = true; 5 | return target; 6 | } 7 | -------------------------------------------------------------------------------- /tests/basic-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('basic', () => { 3 | it('should be defined', () => { 4 | expect(angular2now).toBeDefined(); 5 | }); 6 | it('should be available in window scope', () => { 7 | expect(window.angular2now).toBeDefined(); 8 | }); 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /src/common.js: -------------------------------------------------------------------------------- 1 | export const common = { 2 | angularModule: undefined, 3 | currentModule: undefined, 4 | currentNameSpace: undefined, 5 | isCordova: false, 6 | ng2nOptions: { 7 | currentModule() { 8 | return common.currentModule; 9 | } 10 | }, 11 | controllerAs: undefined, 12 | $q: angular.injector(['ng']).get('$q') 13 | }; 14 | -------------------------------------------------------------------------------- /webpack/test.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./common')({ 2 | entry: './tests/index-spec.js', 3 | output: undefined, 4 | module: { 5 | loaders: [ 6 | // transpile and instrument only testing sources with isparta 7 | { 8 | test: /\.js$/, 9 | include: /src/, 10 | exclude: /node_modules/, 11 | loader: 'isparta' 12 | } 13 | ] 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/exports.js: -------------------------------------------------------------------------------- 1 | angular2now = this.angular2now; 2 | 3 | if (typeof System !== 'undefined' && System.register) { 4 | System.register('angular2now', [], function(_export) { 5 | for (var i in angular2now) { 6 | _export(i, angular2now[i]); 7 | } 8 | 9 | return { 10 | setters: [], 11 | execute: function() { 12 | angular2now.init(); 13 | } 14 | }; 15 | }); 16 | } else { 17 | angular2now.init(); 18 | } 19 | -------------------------------------------------------------------------------- /typings/angular2-now/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": "main", 3 | "tree": { 4 | "src": "https://raw.githubusercontent.com/onderceylan/angular2-now/e22d64ef073c7a460ca75225090b42371fde002c/angular2-now.d.ts", 5 | "raw": "github:onderceylan/angular2-now/angular2-now.d.ts#e22d64ef073c7a460ca75225090b42371fde002c", 6 | "typings": "https://raw.githubusercontent.com/onderceylan/angular2-now/e22d64ef073c7a460ca75225090b42371fde002c/angular2-now.d.ts" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.npm/package/README: -------------------------------------------------------------------------------- 1 | This directory and the files immediately inside it are automatically generated 2 | when you change this package's NPM dependencies. Commit the files in this 3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control 4 | so that others run the same versions of sub-dependencies. 5 | 6 | You should NOT check in the node_modules directory that Meteor automatically 7 | creates; if you are using git, the .gitignore file tells git to ignore it. 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /src/api/controller.js: -------------------------------------------------------------------------------- 1 | import { common } from './../common'; 2 | import { nameSpace } from './../utils'; 3 | 4 | export function Controller(options) { 5 | options = options || {}; 6 | // Allow shorthand notation of just passing the name as a string 7 | if (typeof options === 'string') { 8 | options = { name: options }; 9 | } 10 | 11 | return function ControllerTarget(target) { 12 | angular.module(common.currentModule) 13 | .controller(nameSpace(options.name), target); 14 | return target; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/api/scope-shared.js: -------------------------------------------------------------------------------- 1 | // Cancels out the automatic creation of isolate scope for the directive, 2 | // because Angular 1.x allows only one isolate scope directive per element. 3 | // This is useful when actually creating directives, which add behaviour 4 | // to an existing element, as opposed to components which are stand alone 5 | // bits of html and behaviour. 6 | // The other way to do this is to pass "scope: undefined" to @Component. 7 | export function ScopeShared(target) { 8 | target.scope = undefined; 9 | return target; 10 | } 11 | -------------------------------------------------------------------------------- /tests/local-injectables-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('@LocalInjectables', () => { 3 | it('should add localInjectables to target', () => { 4 | const target = {}; 5 | 6 | angular2now.LocalInjectables(target); 7 | 8 | expect(target.localInjectables).toBe(true); 9 | }); 10 | 11 | it('should return target', () => { 12 | const target = { 13 | selector: 'test' 14 | }; 15 | 16 | expect(angular2now.LocalInjectables(target)).toBe(target); 17 | }); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /src/api/service.js: -------------------------------------------------------------------------------- 1 | import { common } from './../common'; 2 | import { nameSpace } from './../utils'; 3 | 4 | export function Service(options) { 5 | options = options || {}; 6 | // Allow shorthand notation of just passing the name as a string 7 | if (typeof options === 'string') { 8 | options = { 9 | name: options 10 | }; 11 | } 12 | 13 | return function ServiceTarget(target) { 14 | angular.module(common.currentModule) 15 | .service(nameSpace(options.name), target); 16 | // .factory(options.name, function () { return new target }) 17 | 18 | return target; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | cache: 4 | directories: 5 | # cache node modules 6 | - node_modules 7 | notifications: 8 | # disable email notification 9 | email: false 10 | node_js: 11 | # use same node version as meteor does 12 | - "0.12" 13 | before_install: 14 | # use firefox 15 | - "export DISPLAY=:99.0" 16 | - "sh -e /etc/init.d/xvfb start" 17 | before_script: 18 | # remove unused node modules from cache 19 | - "npm prune" 20 | script: 21 | # run karma 22 | - "npm run test" 23 | after_success: 24 | # send code-coverage report to coveralls 25 | - "npm run coverage:coveralls" 26 | -------------------------------------------------------------------------------- /tests/scope-new-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('@ScopeNew()', () => { 3 | it('should set scope as true', () => { 4 | const target = {}; 5 | 6 | angular2now.ScopeNew(target); 7 | 8 | expect(target.scope).toBe(true); 9 | }); 10 | 11 | it('should overwrite scope to true', () => { 12 | const target = { 13 | scope: undefined 14 | }; 15 | 16 | angular2now.ScopeNew(target); 17 | 18 | expect(target.scope).toBe(true); 19 | }); 20 | 21 | it('should return target', () => { 22 | const target = {}; 23 | 24 | const result = angular2now.ScopeShared(target); 25 | 26 | expect(result).toBe(target); 27 | }); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /tests/scope-shared-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('@ScopeShared()', () => { 3 | it('should set scope as undefined', () => { 4 | const target = {}; 5 | 6 | angular2now.ScopeShared(target); 7 | 8 | expect(target.scope).toBeUndefined(); 9 | }); 10 | 11 | it('should overwrite scope to undefined', () => { 12 | const target = { 13 | scope: true 14 | }; 15 | 16 | angular2now.ScopeShared(target); 17 | 18 | expect(target.scope).toBeUndefined(); 19 | }); 20 | 21 | it('should return target', () => { 22 | const target = { 23 | scope: true 24 | }; 25 | 26 | const result = angular2now.ScopeShared(target); 27 | 28 | expect(result).toBe(target); 29 | }); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/api/filter.js: -------------------------------------------------------------------------------- 1 | import { common } from './../common'; 2 | import { nameSpace } from './../utils'; 3 | 4 | export function Filter(options) { 5 | options = options || {}; 6 | // Allow shorthand notation of just passing the name as a string 7 | if (typeof options === 'string') { 8 | options = { 9 | name: options 10 | }; 11 | } 12 | 13 | return function FilterTarget(target) { 14 | filterFunc.$inject = target.$inject; 15 | 16 | angular.module(common.currentModule) 17 | .filter(nameSpace(options.name), filterFunc); 18 | 19 | function filterFunc() { 20 | const args = Array.prototype.slice.call(arguments); 21 | const f = new(Function.prototype.bind.apply(target, [null].concat(args))); 22 | 23 | return f; 24 | } 25 | 26 | return target; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'pbastowski:angular2-now', 3 | version: '1.1.6', 4 | summary: 'Angular 2 @Component syntax for Meteor 1.2 and AngularJS', 5 | git: 'https://github.com/pbastowski/angular2-now.git', 6 | documentation: 'README.md' 7 | }); 8 | 9 | Npm.depends({ 10 | 'angular2-now': '1.1.6' 11 | }); 12 | 13 | Package.onUse(function(api) { 14 | api.versionsFrom('1.2.0.1'); 15 | api.use('angular@1.3.4', 'client'); 16 | api.imply('angular@1.3.4', 'client'); 17 | 18 | // Make sure we load after pbastowski:systemjs, if it's used 19 | api.use('pbastowski:systemjs@0.0.1', 'client', { 20 | weak: true 21 | }); 22 | 23 | api.addFiles([ 24 | '.npm/package/node_modules/angular2-now/dist/angular2-now.js', 25 | 'src/exports.js' 26 | ], 'client', { 27 | transpile: false 28 | }); 29 | 30 | api.export(['angular2now']); 31 | }); 32 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-now", 3 | "version": "1.1.1", 4 | "authors": [ 5 | "pbastowski@gmail.com" 6 | ], 7 | "description": "Angular2 @Component syntax for Angular 1 apps", 8 | "main": "angular2-now.js", 9 | "keywords": [ 10 | "AngularJS", 11 | "angular2-now", 12 | "Angular2", 13 | "babel" 14 | ], 15 | "license": "MIT", 16 | "homepage": "https://github.com/pbastowski/angular2-now", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests", 23 | "angular2-now-tests.js", 24 | ".coverage", 25 | ".docs", 26 | ".meteor", 27 | ".npm", 28 | "plugin", 29 | "test", 30 | ".coveralls.yml", 31 | ".editorconfig", 32 | ".travis.yml", 33 | ".versions", 34 | "package.js", 35 | "package.json", 36 | "versions.json", 37 | ".idea" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-now", 3 | "authors": [ 4 | "pbastowski@gmail.com" 5 | ], 6 | "description": "Use Angular2 @Component syntax with Angular 1.x and Babel", 7 | "main": "angular2-now.js", 8 | "keywords": [ 9 | "angular2", 10 | "angular 2", 11 | "now", 12 | "migration", 13 | "angular1", 14 | "angular 1", 15 | "babel" 16 | ], 17 | "license": "MIT", 18 | "homepage": "https://github.com/pbastowski/angular2-now", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ], 26 | "_release": "9133d274d6", 27 | "_resolution": { 28 | "type": "branch", 29 | "branch": "master", 30 | "commit": "9133d274d67436e739fc82e86b8dc0b7eaf2890e" 31 | }, 32 | "_source": "git://github.com/pbastowski/angular2-now-js.git", 33 | "_target": "*", 34 | "_originalSource": "angular2-now", 35 | "_direct": true 36 | } -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import { common } from './common'; 2 | 3 | // Create a new name from the concatenation of 4 | // the currentNameSpace and the name argument 5 | export function nameSpace(name) { 6 | return common.currentNameSpace ? common.currentNameSpace + '_' + name : name; 7 | } 8 | 9 | export function getService(serviceName, moduleName) { 10 | return angular.module(moduleName || common.currentModule) 11 | ._invokeQueue 12 | .filter((v) => v[0] === '$provide' && v[2][0] === serviceName)[0]; 13 | } 14 | 15 | // Does a provider with a specific name exist in the current module 16 | export function serviceExists(serviceName) { 17 | return !!getService(serviceName); 18 | } 19 | 20 | export function camelCase(s) { 21 | return s.replace(/-(.)/g, (a, b) => b.toUpperCase()); 22 | } 23 | 24 | export function unCamelCase(c) { 25 | const s = c.replace(/([A-Z])/g, '-$1') 26 | .replace(/--/g, '-') 27 | .toLowerCase(); 28 | 29 | if (s[0] === '-') { 30 | return s.slice(1); 31 | } 32 | 33 | return s; 34 | } 35 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "./node_modules/eslint-config-airbnb/rules/best-practices.js", 4 | "./node_modules/eslint-config-airbnb/rules/errors.js", 5 | "./node_modules/eslint-config-airbnb/rules/legacy.js", 6 | "./node_modules/eslint-config-airbnb/rules/node.js", 7 | "./node_modules/eslint-config-airbnb/rules/strict.js", 8 | "./node_modules/eslint-config-airbnb/rules/style.js", 9 | "./node_modules/eslint-config-airbnb/rules/variables.js", 10 | "./node_modules/eslint-config-airbnb/rules/es6.js" 11 | ], 12 | "parser": "babel-eslint", 13 | "env": { 14 | "browser": true, 15 | "meteor": true, 16 | "jasmine": true, 17 | "es6": true 18 | }, 19 | "globals": { 20 | "angular": false, 21 | "inject": false 22 | }, 23 | "rules": { 24 | "new-cap": 1, 25 | "no-unused-vars": 1, 26 | "comma-dangle": 0, 27 | "no-use-before-define": 1, 28 | "no-param-reassign": 1, 29 | "no-nested-ternary": 1, 30 | "dot-notation": 0, 31 | "guard-for-in": 1, 32 | "no-shadow": 1 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/api/set-module.js: -------------------------------------------------------------------------------- 1 | import { common } from './../common'; 2 | 3 | export function SetModule() { 4 | /** 5 | * Name-spacing applies to provider names, not modules. Each module 6 | * has to have a unique name of it's own. 7 | * 8 | * A namespace may be specified like this: 9 | * SetModule('ftdesiree:helpers') 10 | * The namespace, once set, will remain in force until removed. 11 | * Remove the namespace like this: 12 | * angular.module(':helpers') 13 | **/ 14 | common.currentModule = arguments[0].split(':'); 15 | 16 | if (common.currentModule.length === 1) { 17 | // No namespace, just the module name 18 | common.currentModule = common.currentModule[0]; 19 | } else { 20 | // Split off the name-space and module name 21 | common.currentNameSpace = common.currentModule[0]; 22 | common.currentModule = common.currentModule[1]; 23 | 24 | // Reassign arguments[0] without the namespace 25 | arguments[0] = common.currentModule; 26 | } 27 | 28 | return common.angularModule.apply(angular, arguments); 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 pbastowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /webpack/common.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var webpack = require('webpack'); 3 | var pkg = require('../package.json'); 4 | 5 | function concatArrays(objValue, srcValue) { 6 | if (_.isArray(objValue)) { 7 | return _.uniq(objValue.concat(srcValue)); 8 | } 9 | } 10 | 11 | module.exports = function(cfg) { 12 | var common = { 13 | babel: { 14 | presets: ['es2015'] 15 | }, 16 | entry: { 17 | 'dist/angular2-now': './src/angular2-now' 18 | }, 19 | externals: { 20 | angular: 'angular' 21 | }, 22 | module: { 23 | loaders: [{ 24 | test: /\.js$/, 25 | include: [ 26 | /tests/, 27 | /src/ 28 | ], 29 | exclude: /node_modules/, 30 | loader: 'babel' 31 | }] 32 | }, 33 | output: { 34 | libraryTarget: 'umd', 35 | library: 'angular2now' 36 | }, 37 | plugins: [ 38 | new webpack.BannerPlugin(pkg.name + ' v' + pkg.version) 39 | ], 40 | resolve: { 41 | extensions: ['', '.js'] 42 | }, 43 | stats: { 44 | colors: true, 45 | reasons: true 46 | } 47 | }; 48 | 49 | return _.merge({}, common, cfg, concatArrays); 50 | }; 51 | -------------------------------------------------------------------------------- /tests/index-spec.js: -------------------------------------------------------------------------------- 1 | // angular2now 2 | import angular2now from './../src/angular2-now'; 3 | // specs 4 | import basic from './basic-spec'; 5 | import SetModule from './set-module-spec'; 6 | import Inject from './inject-spec'; 7 | import View from './view-spec'; 8 | import Controller from './controller-spec'; 9 | import Service from './service-spec'; 10 | import Filter from './filter-spec'; 11 | import ScopeShared from './scope-shared-spec'; 12 | import ScopeNew from './scope-new-spec'; 13 | import Directive from './directive-spec'; 14 | import Component from './component-spec'; 15 | import bootstrap from './bootstrap-spec'; 16 | import State from './state-spec'; 17 | import MeteorMethod from './meteor-method-spec'; 18 | import MeteorReactive from './meteor-reactive-spec'; 19 | import LocalInjectables from './local-injectables-spec'; 20 | import Options from './options-spec'; 21 | // main test module name 22 | const ngModuleName = 'test'; 23 | // specs in array 24 | const specs = [ 25 | basic, 26 | SetModule, 27 | Inject, 28 | View, 29 | Controller, 30 | Service, 31 | Filter, 32 | ScopeShared, 33 | ScopeNew, 34 | Directive, 35 | Component, 36 | bootstrap, 37 | State, 38 | MeteorMethod, 39 | MeteorReactive, 40 | LocalInjectables, 41 | Options 42 | ]; 43 | // call each spec 44 | specs.forEach((spec) => { 45 | spec(angular2now, ngModuleName); 46 | }); 47 | -------------------------------------------------------------------------------- /src/angular2-now.js: -------------------------------------------------------------------------------- 1 | import { common } from './common'; 2 | import { SetModule } from './api/set-module'; 3 | import { Component } from './api/component'; 4 | import { ScopeShared } from './api/scope-shared'; 5 | import { ScopeNew } from './api/scope-new'; 6 | import { View } from './api/view'; 7 | import { Inject } from './api/inject'; 8 | import { Controller } from './api/controller'; 9 | import { Service } from './api/service'; 10 | import { Filter } from './api/filter'; 11 | import { bootstrap } from './api/bootstrap'; 12 | import { State } from './api/state'; 13 | import { options, Options } from './api/options'; 14 | import { MeteorMethod } from './api/meteor-method'; 15 | import { MeteorReactive } from './api/meteor-reactive'; 16 | import { LocalInjectables } from './api/local-injectables'; 17 | 18 | const angular2now = { 19 | init, 20 | 21 | SetModule, 22 | 23 | Component, 24 | ScopeShared, 25 | ScopeNew, 26 | View, 27 | Inject, 28 | Controller, 29 | Service, 30 | Filter, 31 | bootstrap, 32 | State, 33 | 34 | options, 35 | Options, 36 | 37 | MeteorMethod, 38 | MeteorReactive, 39 | LocalInjectables, 40 | 41 | Directive: Component, 42 | Injectable: Service 43 | }; 44 | 45 | function init() { 46 | common.isCordova = typeof cordova !== 'undefined'; 47 | common.angularModule = angular.module; 48 | } 49 | 50 | if (typeof Meteor === 'undefined') { 51 | init(); 52 | } 53 | 54 | if (typeof window !== 'undefined') { 55 | window.angular2now = angular2now; 56 | } 57 | 58 | export default angular2now; 59 | -------------------------------------------------------------------------------- /src/api/view.js: -------------------------------------------------------------------------------- 1 | export function View(options) { 2 | options = options || {}; 3 | // Allow shorthand notation of just passing the templateUrl as a string 4 | if (typeof options === 'string') { 5 | options = { 6 | templateUrl: options 7 | }; 8 | } 9 | 10 | // if (!options.template) options.template = undefined; 11 | 12 | return function ViewTarget(target) { 13 | target.template = options.template || target.template; 14 | target.templateUrl = options.templateUrl || target.templateUrl; 15 | 16 | // When a templateUrl is specified in options, then transclude can also be specified 17 | target.transclude = options.transclude || target.transclude; 18 | 19 | // directives is an array of child directive controllers (Classes) 20 | target.directives = options.directives || target.directives; 21 | 22 | // Check for the new tag and add ng-transclude to it, if not there. 23 | if (target.template) { 24 | target.template = transcludeContent(target.template); 25 | } 26 | 27 | return target; 28 | }; 29 | 30 | // If template contains the new tag then add ng-transclude to it. 31 | // This will be picked up in @Component, where ddo.transclude will be set to true. 32 | function transcludeContent(template) { 33 | const s = (template || '').match(/\]([^\>]+)/i); 34 | 35 | if (s && s[1].toLowerCase().indexOf('ng-transclude') === -1) { 36 | template = template.replace(/\ { 41 | // Namespace any injectables without an existing nameSpace prefix and also 42 | // not already prefixed with '$', '@' or '@^'. 43 | if (dep[0] !== '$' && dep[0] !== '@' && dep.indexOf('_') === -1) { 44 | dep = nameSpace(dep); 45 | } 46 | 47 | if (injectable.$inject.indexOf(dep) === -1) { 48 | injectable.$inject.push(dep); 49 | } 50 | }); 51 | 52 | if (existingInjects) { 53 | injectable.$inject = injectable.$inject.concat(existingInjects); 54 | } 55 | 56 | return descriptor || target; 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /tests/meteor-reactive-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('@MeteorReactive', () => { 3 | it('should add meteorReactive to target', () => { 4 | const target = {}; 5 | 6 | angular2now.MeteorReactive(target); 7 | 8 | expect(target.meteorReactive).toBe(true); 9 | }); 10 | 11 | it('should return target', () => { 12 | const target = { 13 | selector: 'test' 14 | }; 15 | 16 | expect(angular2now.MeteorReactive(target)).toBe(target); 17 | }); 18 | }); 19 | 20 | describe('on component', () => { 21 | /** 22 | * Mock angular.module() 23 | * @type {Object} 24 | */ 25 | const moduleMock = { 26 | directive: () => {} 27 | }; 28 | const selector = 'test-component'; 29 | /** 30 | * Target used in all tests 31 | */ 32 | let target; 33 | /** 34 | * Spy on angular.module 35 | */ 36 | let spyModule; 37 | /** 38 | * spy on angular.module().directive 39 | */ 40 | let spyDirective; 41 | /** 42 | * Shorthand for angular2now.Component(opts)(target) 43 | * @param {Object|String} opts Component(opts) 44 | * @return {Object} target reference 45 | */ 46 | function doReactiveComponent(opts) { 47 | angular2now.MeteorReactive(target); 48 | 49 | return angular2now.Component(opts)(target); 50 | } 51 | 52 | beforeEach(() => { 53 | // reset target 54 | target = function target() {}; 55 | // add spy on angular.module and return mock 56 | spyModule = spyOn(angular, 'module').and.returnValue(moduleMock); 57 | // add spy on angular.module().directive; 58 | spyDirective = spyOn(moduleMock, 'directive'); 59 | }); 60 | 61 | it('should have $reactive and $scope', () => { 62 | const result = doReactiveComponent(selector); 63 | 64 | expect(result.$inject[0]).toBe('$reactive'); 65 | expect(result.$inject[1]).toBe('$scope'); 66 | }); 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /tests/options-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('Options()', () => { 3 | let target; 4 | 5 | function doOptions(opts) { 6 | return angular2now.Options(opts)(target); 7 | } 8 | 9 | beforeEach(() => { 10 | target = {}; 11 | }); 12 | 13 | it('should merge options', () => { 14 | const spinner = { 15 | show: function showSpinner() {}, 16 | hide: function hideSpinner() {} 17 | }; 18 | const overwriteSpinner = { 19 | show: function showAnotherSpinner() {} 20 | }; 21 | 22 | // set options 23 | doOptions({ 24 | spinner 25 | }); 26 | // expect spinner to be the new one 27 | expect(angular2now.options().spinner.show).toBe(spinner.show); 28 | expect(angular2now.options().spinner.hide).toBe(spinner.hide); 29 | 30 | // update options 31 | doOptions({ 32 | spinner: overwriteSpinner 33 | }); 34 | // expect to be overwritten 35 | expect(angular2now.options().spinner.show).not.toBe(spinner.show); 36 | expect(angular2now.options().spinner.show).toBe(overwriteSpinner.show); 37 | // expect hide to be kept 38 | expect(angular2now.options().spinner.hide).toBe(spinner.hide); 39 | }); 40 | 41 | it('should return target', () => { 42 | const result = doOptions({}); 43 | 44 | expect(result).toBe(target); 45 | }); 46 | 47 | it('should be able to monkey-patch angular.module', () => { 48 | angular2now.options({ 49 | noConflict: true 50 | }); 51 | 52 | expect(angular.module.name).not.toBe(angular2now.SetModule.name); 53 | expect(angular.module.name).toBe('module'); 54 | }); 55 | 56 | it('should be able to not monkey-patch angular.module', () => { 57 | angular2now.options({ 58 | noConflict: false 59 | }); 60 | 61 | expect(angular.module.name).toBe(angular2now.SetModule.name); 62 | expect(angular.module.name).not.toBe('module'); 63 | }); 64 | }); 65 | }; 66 | -------------------------------------------------------------------------------- /tests/set-module-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('SetModule()', () => { 3 | it('should create module', () => { 4 | angular2now.SetModule(ngModuleName, []); 5 | expect(angular.module(ngModuleName)).toBeDefined(); 6 | }); 7 | 8 | it('should have proper name', () => { 9 | angular2now.SetModule(ngModuleName, []); 10 | expect(angular.module(ngModuleName).name).toBe(ngModuleName); 11 | }); 12 | 13 | it('should contain ui.router', () => { 14 | angular2now.SetModule(ngModuleName, ['ui.router']); 15 | expect(angular.module(ngModuleName).requires).toContain('ui.router'); 16 | }); 17 | 18 | it('should keep recently created module as current module', () => { 19 | const recent = `${ngModuleName}.new`; 20 | 21 | angular2now.SetModule(ngModuleName, []); 22 | expect(angular2now.options().currentModule()).toBe(ngModuleName); 23 | angular2now.SetModule(recent, []); 24 | expect(angular2now.options().currentModule()).toBe(recent); 25 | }); 26 | 27 | it('should get module', () => { 28 | angular2now.SetModule(ngModuleName, []); 29 | expect(angular2now.SetModule(ngModuleName)).toBe(angular.module(ngModuleName)); 30 | }); 31 | 32 | it('should overwrite module when using namespace and the same module name', () => { 33 | const nsModule = `ns:${ngModuleName}`; 34 | angular2now.SetModule(nsModule, []); 35 | 36 | // module with ns:test as name should not be available 37 | expect(() => { 38 | angular.module(nsModule); 39 | }).toThrowError(/not available/); 40 | // test should now not contain ui.router 41 | expect(angular2now.SetModule(ngModuleName).requires).not.toContain('ui.router'); 42 | }); 43 | 44 | it('should update current module when new has been created', () => { 45 | const newModule = `${ngModuleName}New`; 46 | 47 | angular2now.SetModule(newModule, []); 48 | expect(angular2now.options().currentModule()).toBe(newModule); 49 | }); 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /src/api/meteor-method.js: -------------------------------------------------------------------------------- 1 | import { common } from './../common'; 2 | 3 | // The name of the Meteor.method is the same as the name of class method. 4 | export function MeteorMethod(_options) { 5 | const options = angular.merge({}, common.ng2nOptions, _options); 6 | let spinner = options.spinner || { 7 | show: angular.noop, 8 | hide: angular.noop 9 | }; 10 | const events = options.events || { 11 | beforeCall: angular.noop, 12 | afterCall: angular.noop 13 | }; 14 | 15 | return function MeteorMethodTarget(target, name, descriptor) { 16 | // Create a method that calls the back-end 17 | descriptor.value = function () { 18 | const argv = Array.prototype.slice.call(arguments); 19 | const deferred = common.$q.defer(); 20 | 21 | if (typeof spinner === 'string') { 22 | if (angular.injector(['ng', common.currentModule]).has(options.spinner)) { 23 | spinner = angular.injector(['ng', common.currentModule]).get(options.spinner); 24 | options.spinner = spinner; 25 | } else { 26 | throw new Error('Spinner "' + spinner + '" does not exist.'); 27 | } 28 | } 29 | 30 | argv.unshift(name); 31 | argv.push(resolver); 32 | 33 | if (spinner) { 34 | spinner.show(); 35 | } 36 | 37 | if (events.beforeCall) { 38 | events.beforeCall(); 39 | } 40 | // Call optional events.beforeCall() 41 | 42 | // todo: should call Meteor after resolution of promise returned by beforeCall() 43 | Meteor.call.apply(this, argv); 44 | 45 | deferred.promise.finally(function () { 46 | spinner.hide(); 47 | // Call optional events.afterCall() 48 | if (events.afterCall) { 49 | events.afterCall(); 50 | } 51 | }); 52 | 53 | return deferred.promise; 54 | 55 | function resolver(err, data) { 56 | if (err) { 57 | deferred.reject(err); 58 | } else { 59 | deferred.resolve(data); 60 | } 61 | } 62 | }; 63 | 64 | return descriptor; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /tests/service-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('@Service()', () => { 3 | const name = 'TestService'; 4 | const moduleMock = { 5 | service() {} 6 | }; 7 | let spy; 8 | let spyService; 9 | 10 | function Target() {} 11 | 12 | beforeEach(() => { 13 | // set spies 14 | spy = spyOn(angular, 'module').and.returnValue(moduleMock); 15 | spyService = spyOn(moduleMock, 'service'); 16 | }); 17 | 18 | describe('with namespace', () => { 19 | beforeEach(() => { 20 | // set ngModuleName as currentModule 21 | angular2now.SetModule(`ns:${ngModuleName}`, []); 22 | }); 23 | 24 | it('should set service if argument is a string', () => { 25 | angular2now.Service(name)(Target); 26 | 27 | expect(spy).toHaveBeenCalledWith(ngModuleName); 28 | expect(spyService).toHaveBeenCalledWith(`ns_${name}`, Target); 29 | }); 30 | 31 | it('should set service if argument is an object with name property', () => { 32 | angular2now.Service({ 33 | name 34 | })(Target); 35 | 36 | expect(spy).toHaveBeenCalledWith(ngModuleName); 37 | expect(spyService).toHaveBeenCalledWith(`ns_${name}`, Target); 38 | }); 39 | }); 40 | 41 | describe('without namespace', () => { 42 | beforeEach(() => { 43 | // set ngModuleName as currentModule 44 | angular2now.SetModule(`:${ngModuleName}`, []); 45 | }); 46 | 47 | it('should set service if argument is a string', () => { 48 | angular2now.Service(name)(Target); 49 | 50 | expect(spy).toHaveBeenCalledWith(ngModuleName); 51 | expect(spyService).toHaveBeenCalledWith(name, Target); 52 | }); 53 | 54 | it('should set service if argument is an object with name property', () => { 55 | angular2now.Service({ 56 | name 57 | })(Target); 58 | 59 | expect(spy).toHaveBeenCalledWith(ngModuleName); 60 | expect(spyService).toHaveBeenCalledWith(name, Target); 61 | }); 62 | }); 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /karma.config.js: -------------------------------------------------------------------------------- 1 | require('argv-set-env')(); 2 | 3 | // webpack configuration 4 | var webpack = require('./webpack')('test'); 5 | 6 | // main file with tests 7 | var testFile = 'tests/index-spec.js'; 8 | 9 | // is it Continuous Integration environment 10 | var ciEnv = process.env.NODE_ENV === 'ci'; 11 | 12 | // add preprocessors 13 | var preprocessors = {}; 14 | preprocessors[testFile] = ['webpack']; 15 | 16 | module.exports = function(config) { 17 | var _config = { 18 | 19 | // base path that will be used to resolve all patterns (eg. files, exclude) 20 | basePath: '', 21 | 22 | 23 | // frameworks to use 24 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 25 | frameworks: ['jasmine'], 26 | 27 | 28 | // list of files / patterns to load in the browser 29 | files: [ 30 | 'node_modules/angular/angular.js', 31 | 'node_modules/angular-mocks/angular-mocks.js', 32 | 'node_modules/angular-ui-router/release/angular-ui-router.js', 33 | testFile 34 | ], 35 | 36 | 37 | // list of files to exclude 38 | exclude: [], 39 | 40 | 41 | // preprocess matching files before serving them to the browser 42 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 43 | preprocessors: preprocessors, 44 | 45 | webpack: webpack, 46 | 47 | coverageReporter: { 48 | reporters: [{ 49 | type: 'lcov', 50 | dir: 'coverage/', 51 | subdir: '.' 52 | }, { 53 | type: 'json', 54 | dir: 'coverage/', 55 | subdir: '.' 56 | }, { 57 | type: 'text-summary' 58 | }] 59 | }, 60 | 61 | // test results reporter to use 62 | // possible values: 'dots', 'progress' 63 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 64 | reporters: ['progress', 'coverage', 'kjhtml'], 65 | 66 | 67 | // web server port 68 | port: 9876, 69 | 70 | 71 | // enable / disable colors in the output (reporters and logs) 72 | colors: true, 73 | 74 | 75 | // level of logging 76 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 77 | logLevel: config.LOG_INFO, 78 | 79 | 80 | // enable / disable watching file and executing tests whenever any file changes 81 | autoWatch: !ciEnv, 82 | 83 | 84 | // start these browsers 85 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 86 | browsers: [(ciEnv ? 'Firefox' : 'Chrome')], 87 | 88 | 89 | // Continuous Integration mode 90 | // if true, Karma captures browsers, runs the tests and exits 91 | singleRun: ciEnv 92 | }; 93 | config.set(_config); 94 | }; 95 | -------------------------------------------------------------------------------- /typings/angular2-now/index.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by typings 2 | // Source: https://raw.githubusercontent.com/onderceylan/angular2-now/e22d64ef073c7a460ca75225090b42371fde002c/angular2-now.d.ts 3 | declare module 'angular2-now' { 4 | export function init(); 5 | 6 | export function options(config: options); 7 | 8 | export function Component(config: ComponentConfig): ClassDecorator; 9 | 10 | export function Service(config: ServiceConfig|string): ClassDecorator; 11 | 12 | export function Filter(config: FilterConfig|string): ClassDecorator; 13 | 14 | export function State(config: StateConfig): ClassDecorator; 15 | 16 | export function View(config: ViewConfig|string): ClassDecorator; 17 | 18 | export function bootstrap(appName: any, dependencies?: string[]); 19 | 20 | export function SetModule(appName: string, dependencies?: string[]); 21 | 22 | export function Inject(dependencies: string[]); 23 | 24 | export function MeteorMethod(config?: options); 25 | 26 | interface options { 27 | controllerAs?: string; 28 | spinner?: any; 29 | events?: any; 30 | noConflict?: boolean; 31 | } 32 | 33 | interface ServiceConfig { 34 | name: string; 35 | } 36 | 37 | interface StateConfig { 38 | name: string; // 'stateName' 39 | url?: string; // '/stateurl' 40 | defaultRoute?: boolean|string; // true/false or '/default/route/url' 41 | abstract?: boolean; 42 | html5Mode?: boolean; 43 | params?: any; // { id: 123 }, // default params, see ui-router docs 44 | data?: any; // { a: 1, b: 2}, // custom data 45 | resolve?: any; 46 | controller?: ControllerClass; 47 | template?: any; // '
' 48 | templateUrl?: string; // 'client/app/app.html' 49 | templateProvider?: Function; // function() { return "

content

"; } 50 | } 51 | 52 | interface ViewConfig { 53 | template?: string; 54 | templateUrl?: string; 55 | transclude?: boolean; 56 | } 57 | 58 | interface ComponentConfig { 59 | selector: string; // 'my-app' 60 | template?: any; // '
Inline template
' 61 | templateUrl?: string; // 'path/to/the_template.html' 62 | bind?: Bind; // { twoWay: '=', value: '@', function: '&' }, 63 | providers?: string[]; 64 | replace?: boolean; 65 | transclude?: boolean; 66 | scope?: undefined|boolean|Bind; 67 | } 68 | 69 | interface Bind { 70 | [id: string]: any; 71 | } 72 | 73 | interface FilterConfig { 74 | name: string; 75 | } 76 | 77 | interface ControllerClass extends Function { 78 | template?: string|Function; 79 | templateUrl?: string|Function; 80 | link?: Function; 81 | compile?: any; 82 | } 83 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-now", 3 | "version": "1.1.6", 4 | "description": "Angular 2 component syntax for Angular 1 apps", 5 | "main": "dist/angular2-now.js", 6 | "scripts": { 7 | "meteor": "gulp meteor", 8 | "test": "karma start karma.config.js --set-env-NODE_ENV=ci", 9 | "test:local": "karma start karma.config.js", 10 | "lint": "eslint src/**/*.js", 11 | "build:dist": "webpack --progress", 12 | "build:prod": "webpack --progress --set-env-NODE_ENV=production", 13 | "build": "npm run build:dist && npm run build:prod", 14 | "bump:patch": "gulp bump:patch && npm run after-bump", 15 | "bump:minor": "gulp bump:minor && npm run after-bump", 16 | "bump:major": "gulp bump:major && npm run after-bump", 17 | "after-bump": "npm run meteor", 18 | "release:patch": "npm run bump:patch && npm run build", 19 | "release:minor": "npm run bump:minor && npm run build", 20 | "release:major": "npm run bump:major && npm run build", 21 | "coverage:coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/pbastowski/angular2-now.git" 26 | }, 27 | "keywords": [ 28 | "angular", 29 | "angular1", 30 | "angular2", 31 | "annotations", 32 | "decorators", 33 | "babel" 34 | ], 35 | "author": "Paul Bastowski (https://github.com/pbastowski)", 36 | "maintainers": [ 37 | { 38 | "name": "pbastowski", 39 | "email": "" 40 | }, 41 | { 42 | "name": "kamilkisiela", 43 | "email": "" 44 | } 45 | ], 46 | "license": "MIT", 47 | "bugs": { 48 | "url": "https://github.com/pbastowski/angular2-now/issues" 49 | }, 50 | "homepage": "https://github.com/pbastowski/angular2-now", 51 | "devDependencies": { 52 | "angular": "^1.4.0", 53 | "angular-mocks": "^1.4.0", 54 | "angular-ui-router": "^0.2.15", 55 | "argv-set-env": "^1.0.0", 56 | "babel": "^6.3.26 ", 57 | "babel-core": "^6.4.5", 58 | "babel-eslint": "^5.0.0--beta", 59 | "babel-loader": "^6.2.1", 60 | "babel-preset-es2015": "^6.3.13", 61 | "coveralls": "^2.11.4", 62 | "eslint": "^1.10.3", 63 | "eslint-config-airbnb": "^4.0.0", 64 | "gulp": "^3.9.0", 65 | "gulp-bump": "^1.0.0", 66 | "gulp-replace": "0.5.4", 67 | "isparta": "^4.0.0", 68 | "isparta-loader": "^2.0.0", 69 | "jasmine-core": "^2.3.4", 70 | "karma": "^0.13.19", 71 | "karma-babel-preprocessor": "^6.0.1", 72 | "karma-chrome-launcher": "^0.2.1", 73 | "karma-coverage": "^0.5.3", 74 | "karma-firefox-launcher": "^0.1.7", 75 | "karma-jasmine": "^0.3.6", 76 | "karma-jasmine-html-reporter": "^0.2.0", 77 | "karma-sourcemap-loader": "^0.3.6", 78 | "karma-webpack": "^1.7.0", 79 | "lodash": "^3.10.1", 80 | "webpack": "^1.12.9" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/controller-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('@Controller()', () => { 3 | /** 4 | * controller name 5 | * @type {String} 6 | */ 7 | const name = 'TestCtrl'; 8 | /** 9 | * Mock angular.module 10 | * @type {Object} 11 | */ 12 | const moduleMock = { 13 | controller() { 14 | 15 | } 16 | }; 17 | /** 18 | * spy on angular.module 19 | */ 20 | let spyModule; 21 | /** 22 | * spy on angular.module().directive 23 | */ 24 | let spyCtrl; 25 | 26 | /** 27 | * Target used in decorator 28 | */ 29 | function Target() {} 30 | 31 | /** 32 | * Shorthand for decorator call on target 33 | * @param {Any} opts options 34 | * @return {Target} 35 | */ 36 | function doController(opts) { 37 | return angular2now.Controller(opts)(Target); 38 | } 39 | 40 | 41 | beforeEach(() => { 42 | // set spies 43 | // and return mock 44 | spyModule = spyOn(angular, 'module').and.returnValue(moduleMock); 45 | spyCtrl = spyOn(moduleMock, 'controller'); 46 | }); 47 | 48 | describe('with namespace', () => { 49 | beforeEach(() => { 50 | // set ngModuleName as currentModule 51 | angular2now.SetModule(`ns:${ngModuleName}`, []); 52 | }); 53 | 54 | it('should set name if argument is a string', () => { 55 | doController(name); 56 | 57 | expect(spyModule).toHaveBeenCalledWith(ngModuleName); 58 | expect(spyCtrl).toHaveBeenCalledWith(`ns_${name}`, Target); 59 | }); 60 | 61 | it('should set name if argument is an object with name property', () => { 62 | doController({ 63 | name 64 | }); 65 | 66 | expect(spyModule).toHaveBeenCalledWith(ngModuleName); 67 | expect(spyCtrl).toHaveBeenCalledWith(`ns_${name}`, Target); 68 | }); 69 | }); 70 | 71 | describe('without namespace', () => { 72 | beforeEach(() => { 73 | // set ngModuleName as currentModule 74 | angular2now.SetModule(`:${ngModuleName}`, []); 75 | }); 76 | 77 | it('should set name if argument is a string', () => { 78 | // angular2now.Controller(name)(Target); 79 | doController(name); 80 | 81 | expect(spyModule).toHaveBeenCalledWith(ngModuleName); 82 | expect(spyCtrl).toHaveBeenCalledWith(name, Target); 83 | }); 84 | 85 | it('should set name if argument is an object with name property', () => { 86 | doController({ 87 | name 88 | }); 89 | 90 | expect(spyModule).toHaveBeenCalledWith(ngModuleName); 91 | expect(spyCtrl).toHaveBeenCalledWith(name, Target); 92 | }); 93 | }); 94 | 95 | it('should return target', () => { 96 | const result = doController({ 97 | name 98 | }); 99 | 100 | expect(result).toBe(Target); 101 | }); 102 | }); 103 | }; 104 | -------------------------------------------------------------------------------- /tests/view-spec.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default (angular2now, ngModuleName) => { 4 | describe('@View()', () => { 5 | it('should set templateUrl if argument is a string', () => { 6 | const target = {}; 7 | const templateUrl = 'test.html'; 8 | 9 | angular2now.View(templateUrl)(target); 10 | 11 | expect(target.templateUrl).toBe(templateUrl); 12 | }); 13 | 14 | it('should overwrite target\'s templateUrl if argument is a string', () => { 15 | const templateUrl = 'test.html'; 16 | const target = { 17 | templateUrl: `old-${templateUrl}` 18 | }; 19 | 20 | angular2now.View(templateUrl)(target); 21 | 22 | expect(target.templateUrl).toBe(templateUrl); 23 | }); 24 | 25 | it('should overwrite target\'s templateUrl if specified in options', () => { 26 | const templateUrl = 'test.html'; 27 | const target = { 28 | templateUrl: `old-${templateUrl}` 29 | }; 30 | 31 | angular2now.View({ 32 | templateUrl 33 | })(target); 34 | 35 | expect(target.templateUrl).toBe(templateUrl); 36 | }); 37 | 38 | it('should set template', () => { 39 | const template = 'foobar'; 40 | const target = {}; 41 | 42 | angular2now.View({ 43 | template 44 | })(target); 45 | 46 | expect(target.template).toBe(template); 47 | }); 48 | 49 | it('should overwrite target\'s template', () => { 50 | const template = 'foobar'; 51 | const target = { 52 | template: `old-${template}` 53 | }; 54 | 55 | angular2now.View({ 56 | template 57 | })(target); 58 | 59 | expect(target.template).toBe(template); 60 | }); 61 | 62 | it('should set transclude', () => { 63 | const transclude = true; 64 | const target = {}; 65 | 66 | angular2now.View({ 67 | transclude 68 | })(target); 69 | 70 | expect(target.transclude).toBe(transclude); 71 | }); 72 | 73 | it('should overwrite transclude', () => { 74 | const transclude = true; 75 | const target = { 76 | transclude: false 77 | }; 78 | 79 | angular2now.View({ 80 | transclude 81 | })(target); 82 | 83 | expect(target.transclude).toBe(transclude); 84 | }); 85 | 86 | it('should transclude content directive if available', () => { 87 | const template = ` 88 |
89 | 90 |
91 | `; 92 | const target = { 93 | template 94 | }; 95 | 96 | angular2now.View()(target); 97 | 98 | expect(target.template).toContain('ng-transclude'); 99 | }); 100 | 101 | it('should set directives', () => { 102 | const directives = ['directive']; 103 | const target = {}; 104 | 105 | angular2now.View({ 106 | directives 107 | })(target); 108 | 109 | expect(target.directives).toBe(directives); 110 | }); 111 | 112 | it('should overwrite directives', () => { 113 | const directives = ['directive']; 114 | const target = { 115 | directives: _.map(directives, (d) => `old-${d}`) 116 | }; 117 | 118 | angular2now.View({ 119 | directives 120 | })(target); 121 | 122 | expect(target.directives).toBe(directives); 123 | }); 124 | }); 125 | }; 126 | -------------------------------------------------------------------------------- /tests/bootstrap-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('bootstrap()', () => { 3 | /** 4 | * angular.element mock 5 | * @type {Object} 6 | */ 7 | const elementMock = { 8 | ready() {}, 9 | on() {} 10 | }; 11 | /** 12 | * spy on angular.module 13 | */ 14 | let spyModule; 15 | /** 16 | * spy on angular.bootstrap 17 | */ 18 | let spyBootstrap; 19 | /** 20 | * spy on document.querySelector 21 | */ 22 | let spyDocumentQuery; 23 | /** 24 | * spy on angular.element 25 | */ 26 | let spyElement; 27 | /** 28 | * spy on angular.element().ready 29 | */ 30 | let spyElementReady; 31 | /** 32 | * spy on angular.element().on 33 | */ 34 | let spyElementOn; 35 | 36 | /** 37 | * Call element's 'on' or 'ready'. Depends on Cordova 38 | * @return {[type]} [description] 39 | */ 40 | function callOnReady() { 41 | spyElementReady.calls.mostRecent().args[0](); 42 | } 43 | 44 | beforeEach(() => { 45 | spyModule = spyOn(angular, 'module'); 46 | // return custom value 47 | spyBootstrap = spyOn(angular, 'bootstrap').and.returnValue(true); 48 | spyDocumentQuery = spyOn(document, 'querySelector'); 49 | // return angular.element() mock 50 | spyElement = spyOn(angular, 'element').and.returnValue(elementMock); 51 | // set spies on this mock 52 | spyElementReady = spyOn(elementMock, 'ready'); 53 | spyElementOn = spyOn(elementMock, 'on'); 54 | }); 55 | 56 | describe('not cordova', () => { 57 | it('should use ready on document if target is not defined', () => { 58 | angular2now.bootstrap(); 59 | 60 | expect(spyElement).toHaveBeenCalledWith(document); 61 | expect(spyElementReady).toHaveBeenCalledWith(jasmine.any(Function)); 62 | }); 63 | 64 | it('should use document\'s body if target is not defined', () => { 65 | angular2now.bootstrap(); 66 | callOnReady(); 67 | 68 | // bootstrap on document's body 69 | expect(spyBootstrap.calls.mostRecent().args[0]).toBe(document.body); 70 | }); 71 | 72 | it('should use target\'s selector', () => { 73 | const selector = 'test-selector'; 74 | 75 | angular2now.bootstrap({ 76 | selector 77 | }); 78 | 79 | spyDocumentQuery.and 80 | .returnValue(selector); 81 | 82 | callOnReady(); 83 | 84 | expect(spyDocumentQuery).toHaveBeenCalledWith(selector); 85 | expect(spyBootstrap.calls.mostRecent().args[0]).toBe(selector); 86 | }); 87 | 88 | it('should handle selector provided directly instead of options object', () => { 89 | const selector = 'test-selector'; 90 | 91 | // bootstrap 92 | angular2now.bootstrap(selector); 93 | // add mock 94 | spyDocumentQuery.and 95 | .returnValue(selector); 96 | 97 | callOnReady(); 98 | 99 | // expectations 100 | expect(spyDocumentQuery).toHaveBeenCalledWith(selector); 101 | expect(spyBootstrap.calls.mostRecent().args[0]).toBe(selector); 102 | }); 103 | 104 | it('should use current module if target is function', () => { 105 | const target = function () {}; 106 | 107 | angular2now.bootstrap(target); 108 | 109 | callOnReady(); 110 | 111 | expect(spyBootstrap.calls.mostRecent().args[0]).toBe(document.body); 112 | }); 113 | }); 114 | }); 115 | }; 116 | -------------------------------------------------------------------------------- /tests/filter-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('@Filter()', () => { 3 | /** 4 | * Filter name 5 | * @type {String} 6 | */ 7 | const name = 'TestFilter'; 8 | /** 9 | * Mock angular.module 10 | * @type {Object} 11 | */ 12 | const moduleMock = { 13 | filter() {} 14 | }; 15 | /** 16 | * spy on angular.module 17 | */ 18 | let spyModule; 19 | /** 20 | * spy on angular.module().filter 21 | */ 22 | let spyFilter; 23 | const foo = 'bar'; 24 | 25 | /** 26 | * Target used in all tests 27 | */ 28 | function Target() { 29 | return arguments; 30 | } 31 | // inject default service 32 | Target.$inject = ['$http']; 33 | 34 | function doFilter(opts) { 35 | return angular2now.Filter(opts)(Target); 36 | } 37 | 38 | beforeEach(() => { 39 | // set spies 40 | // and return mock 41 | spyModule = spyOn(angular, 'module').and.returnValue(moduleMock); 42 | spyFilter = spyOn(moduleMock, 'filter'); 43 | }); 44 | 45 | describe('with namespace', () => { 46 | beforeEach(() => { 47 | // set ngModuleName as currentModule 48 | angular2now.SetModule(`ns:${ngModuleName}`, []); 49 | }); 50 | 51 | it('should set filter if argument is a string', () => { 52 | doFilter(name); 53 | 54 | expect(spyModule).toHaveBeenCalledWith(ngModuleName); 55 | expect(spyFilter).toHaveBeenCalledWith(`ns_${name}`, jasmine.any(Function)); 56 | }); 57 | 58 | it('should set filter if argument is an object with name property', () => { 59 | doFilter({ 60 | name 61 | }); 62 | 63 | expect(spyModule).toHaveBeenCalledWith(ngModuleName); 64 | expect(spyFilter).toHaveBeenCalledWith(`ns_${name}`, jasmine.any(Function)); 65 | }); 66 | }); 67 | 68 | describe('without namespace', () => { 69 | beforeEach(() => { 70 | // set ngModuleName as currentModule 71 | angular2now.SetModule(`:${ngModuleName}`, []); 72 | }); 73 | 74 | it('should set filter if argument is a string', () => { 75 | doFilter(name); 76 | 77 | expect(spyModule).toHaveBeenCalledWith(ngModuleName); 78 | expect(spyFilter).toHaveBeenCalledWith(name, jasmine.any(Function)); 79 | }); 80 | 81 | it('should set filter if argument is an object with name property', () => { 82 | doFilter({ 83 | name 84 | }); 85 | 86 | expect(spyModule).toHaveBeenCalledWith(ngModuleName); 87 | expect(spyFilter).toHaveBeenCalledWith(name, jasmine.any(Function)); 88 | }); 89 | }); 90 | 91 | it('should pass target\'s arguments to filter function', () => { 92 | angular2now.SetModule(`:${ngModuleName}`, []); 93 | doFilter({ 94 | name 95 | }); 96 | 97 | // call filter function 98 | const args = spyFilter.calls.mostRecent().args[1]('foo', 'bar'); 99 | 100 | // check arguments 101 | expect(args[0]).toBe('foo'); 102 | expect(args[1]).toBe('bar'); 103 | }); 104 | 105 | it('should copy injectables to filter function', () => { 106 | angular2now.SetModule(`:${ngModuleName}`, []); 107 | doFilter({ 108 | name 109 | }); 110 | 111 | // call filter function 112 | const func = spyFilter.calls.mostRecent().args[1]; 113 | 114 | expect(func.$inject).toEqual(Target.$inject); 115 | }); 116 | 117 | it('should return the same target', () => { 118 | const result = doFilter({ 119 | name 120 | }); 121 | 122 | expect(result).toBe(Target); 123 | }); 124 | }); 125 | }; 126 | -------------------------------------------------------------------------------- /tests/inject-spec.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default (angular2now, ngModuleName) => { 4 | describe('@Inject()', () => { 5 | // TODO add tests of null, undefined and empty array as Inject() argument 6 | it('should throw error on empty dependencies', () => { 7 | expect(() => { 8 | angular2now.Inject(); 9 | }).toThrowError(Error, /dependencies/); 10 | }); 11 | 12 | it('should pass dependencies as an array', () => { 13 | expect(() => { 14 | angular2now.Inject(['$http', '$q']); 15 | }).not.toThrowError(Error, /dependencies/); 16 | }); 17 | 18 | it('should pass dependencies directly', () => { 19 | expect(() => { 20 | angular2now.Inject('$http', '$q'); 21 | }).not.toThrowError(Error, /dependencies/); 22 | }); 23 | 24 | it('should fail on missing target without descriptor', () => { 25 | expect(() => { 26 | angular2now.Inject(['$http', '$q'])(); 27 | }).toThrowError(TypeError, /class/); 28 | }); 29 | 30 | it('should fail on missing descriptor without target', () => { 31 | expect(() => { 32 | angular2now.Inject(['$http', '$q'])(undefined, undefined, undefined); 33 | }).toThrowError(TypeError, /class/); 34 | }); 35 | 36 | describe('with target or descriptor', () => { 37 | // Services 38 | const Injectables = { 39 | target: ['$log', '$q'], 40 | inject: ['$http', '$http', '$q', 'customService'] 41 | }; 42 | const concatedRaw = Injectables.inject.concat(Injectables.target); 43 | const concated = _.uniq( 44 | _.filter(concatedRaw, (inj) => { 45 | return inj[0] === '$'; 46 | }) 47 | ); 48 | // mocks 49 | function target() {} 50 | let descriptor; 51 | 52 | function doInject(useDescriptor) { 53 | if (useDescriptor) { 54 | return angular2now.Inject(Injectables.inject)(undefined, undefined, descriptor); 55 | } 56 | return angular2now.Inject(Injectables.inject)(target); 57 | } 58 | 59 | beforeEach(() => { 60 | angular2now.SetModule(`ns:${ngModuleName}`, []); 61 | target.$inject = Injectables.target; 62 | descriptor = { 63 | value() {} 64 | }; 65 | }); 66 | 67 | it('should return target', () => { 68 | expect(doInject()).toBe(target); 69 | }); 70 | 71 | it('should return descriptor', () => { 72 | expect(doInject(true)).toBe(descriptor); 73 | }); 74 | 75 | it('should concat injectables', () => { 76 | doInject(); 77 | // check if all injectables have been added 78 | concated.forEach((inj) => { 79 | expect(target.$inject.indexOf(inj)).not.toBe(-1); 80 | }); 81 | }); 82 | 83 | it('should add namespace to injectables', () => { 84 | const namespaced = _.reject(concatedRaw, (inj) => inj[0] === '$'); 85 | 86 | doInject(); 87 | 88 | // check with namespace prefix 89 | namespaced.forEach((inj) => { 90 | expect(target.$inject.indexOf(`ns_${inj}`)).not.toBe(-1); 91 | }); 92 | }); 93 | 94 | it('should use descriptor if available', () => { 95 | expect(descriptor.value.$inject).toBeUndefined(); 96 | doInject(true); 97 | expect(descriptor.value.$inject).toBeDefined(); 98 | }); 99 | 100 | it('should keep injectables while extending class', () => { 101 | class parentClass {} 102 | angular2now.Inject(['$http'])(parentClass); 103 | class childClass extends parentClass {} 104 | 105 | expect(childClass.$inject).toContain('$http'); 106 | }); 107 | 108 | it('should keep injectables while extending component without injectables', () => { 109 | const injectables = ['$http', '$foo', '$bar']; 110 | // parent class 111 | class parentClass {} 112 | // inject $http 113 | angular2now.Inject(injectables)(parentClass); 114 | 115 | // child class 116 | class childClass extends parentClass {} 117 | // component extended by parentClass 118 | const result = angular2now.Component({ 119 | selector: 'child-class' 120 | })(childClass); 121 | 122 | injectables.forEach((inj) => { 123 | expect(result.$inject).toContain(inj); 124 | }); 125 | }); 126 | 127 | it('should keep injectables while extending component with injectables', () => { 128 | const injectables = { 129 | parent: ['$http', '$foo', '$bar'], 130 | child: ['$baz'] 131 | }; 132 | // parent class 133 | class parentClass {} 134 | // inject $http 135 | angular2now.Inject(injectables.parent)(parentClass); 136 | 137 | // child class 138 | class childClass extends parentClass {} 139 | 140 | angular2now.Inject(injectables.child)(childClass); 141 | // component extended by parentClass 142 | const result = angular2now.Component({ 143 | selector: 'child-class' 144 | })(childClass); 145 | 146 | injectables.parent.concat(injectables.child).forEach((inj) => { 147 | expect(result.$inject).toContain(inj); 148 | }); 149 | }); 150 | }); 151 | }); 152 | }; 153 | -------------------------------------------------------------------------------- /tests/meteor-method-spec.js: -------------------------------------------------------------------------------- 1 | window.Meteor = { 2 | call(name, callback) { 3 | callback(); 4 | } 5 | }; 6 | 7 | export default (angular2now, ngModuleName) => { 8 | describe('@MeteorMethod()', () => { 9 | const name = 'foo'; 10 | let descriptor; 11 | let spyCall; 12 | 13 | // injectables 14 | let $rootScope; 15 | 16 | /** 17 | * Call @MeterMethod() with options on named descriptor 18 | * @param {Any} opts Options 19 | * @return {Object} descriptor 20 | */ 21 | function doMeteorMethod(opts) { 22 | return angular2now.MeteorMethod(opts)(undefined, name, descriptor); 23 | } 24 | 25 | /** 26 | * Calls both decorator and the method 27 | * @param {Any} opts Options 28 | * @return {Promise} 29 | */ 30 | function runMeteorMethod(opts) { 31 | const desc = doMeteorMethod(opts); 32 | 33 | return desc.value(); 34 | } 35 | 36 | /** 37 | * Resolve called method 38 | * @param {Any} data Resolve data 39 | */ 40 | function resolveMethod(data) { 41 | spyCall.calls.mostRecent().args[1](undefined, data); 42 | } 43 | 44 | /** 45 | * Reject called method 46 | * @param {Error} error Reject with Error 47 | */ 48 | function rejectMethod(error) { 49 | spyCall.calls.mostRecent().args[1](error); 50 | } 51 | 52 | beforeEach(() => { 53 | // set module 54 | angular2now.SetModule(`:${ngModuleName}`, []); 55 | // reset descriptor 56 | descriptor = { 57 | value() {} 58 | }; 59 | 60 | // load module 61 | window.module(ngModuleName); 62 | 63 | // get $rootScope to use digest 64 | inject((_$rootScope_) => { 65 | $rootScope = _$rootScope_; 66 | }); 67 | 68 | // set spy on Meteor.call 69 | spyCall = spyOn(Meteor, 'call'); 70 | }); 71 | 72 | it('should call meteor method', () => { 73 | runMeteorMethod(); 74 | 75 | expect(spyCall).toHaveBeenCalledWith(name, jasmine.any(Function)); 76 | }); 77 | 78 | it('should resolve meteor method', () => { 79 | const data = 'foo'; 80 | const result = runMeteorMethod(); 81 | 82 | // should have been called 83 | expect(spyCall).toHaveBeenCalledWith(name, jasmine.any(Function)); 84 | // expect pending status 85 | expect(result.$$state.status).toBe(0); 86 | 87 | // now emulate method call 88 | resolveMethod(data); 89 | 90 | // expect resolved 91 | expect(result.$$state.status).toBe(1); 92 | // with data 93 | expect(result.$$state.value).toBe(data); 94 | }); 95 | 96 | it('should reject meteor method', () => { 97 | const error = 'bar'; 98 | const result = runMeteorMethod(); 99 | 100 | // should have been called 101 | expect(spyCall).toHaveBeenCalledWith(name, jasmine.any(Function)); 102 | // expect pending status 103 | expect(result.$$state.status).toBe(0); 104 | 105 | // now emulate method call 106 | rejectMethod(error); 107 | 108 | // expect rejected 109 | expect(result.$$state.status).toBe(2); 110 | // with error 111 | expect(result.$$state.value).toBe(error); 112 | }); 113 | 114 | it('should call beforeCall', () => { 115 | const beforeCall = jasmine.createSpy('beforeCall'); 116 | 117 | runMeteorMethod({ 118 | events: { 119 | beforeCall 120 | } 121 | }); 122 | 123 | expect(spyCall).toHaveBeenCalled(); 124 | expect(beforeCall).toHaveBeenCalled(); 125 | }); 126 | 127 | describe('afterCall', () => { 128 | let afterCall; 129 | let promise; 130 | 131 | beforeEach(() => { 132 | afterCall = jasmine.createSpy('afterCall'); 133 | 134 | promise = runMeteorMethod({ 135 | events: { 136 | afterCall 137 | } 138 | }); 139 | }); 140 | 141 | it('should be called when resolved', (done) => { 142 | expect(spyCall).toHaveBeenCalled(); 143 | expect(afterCall).not.toHaveBeenCalled(); 144 | 145 | resolveMethod('test-resolved'); 146 | 147 | promise.finally(() => { 148 | expect(afterCall).toHaveBeenCalled(); 149 | }); 150 | promise.finally(done); 151 | }); 152 | 153 | it('should be called when rejected', (done) => { 154 | expect(spyCall).toHaveBeenCalled(); 155 | expect(afterCall).not.toHaveBeenCalled(); 156 | 157 | rejectMethod('test-rejected'); 158 | 159 | promise.finally(() => { 160 | expect(afterCall).toHaveBeenCalled(); 161 | }); 162 | promise.finally(done); 163 | }); 164 | }); 165 | 166 | it('should set spinner service', (done) => { 167 | const spinner = 'testSpinner'; 168 | const spyShow = jasmine.createSpy('showSpinner'); 169 | const spyHide = jasmine.createSpy('hideSpinner'); 170 | 171 | // add new service with spinner 172 | angular2now.SetModule('test', []).service(spinner, function testSpinner() { 173 | this.show = spyShow; 174 | this.hide = spyHide; 175 | }); 176 | 177 | // run meteor method 178 | const promise = runMeteorMethod({ 179 | spinner 180 | }); 181 | 182 | // resolve it 183 | resolveMethod('test-resolved'); 184 | 185 | // check if called 186 | promise.finally(() => { 187 | expect(spyShow).toHaveBeenCalled(); 188 | expect(spyHide).toHaveBeenCalled(); 189 | }); 190 | // end async test 191 | promise.finally(done); 192 | }); 193 | 194 | it('should fail on non existing spinner', () => { 195 | expect(() => { 196 | runMeteorMethod({ 197 | spinner: 'foobarspinner' 198 | }); 199 | }).toThrowError(Error, /spinner/i); 200 | }); 201 | 202 | it('should show spinner', () => { 203 | const show = jasmine.createSpy('show'); 204 | 205 | runMeteorMethod({ 206 | spinner: { 207 | show, 208 | hide() {} 209 | } 210 | }); 211 | 212 | expect(spyCall).toHaveBeenCalled(); 213 | expect(show).toHaveBeenCalled(); 214 | }); 215 | 216 | describe('hide spinner', () => { 217 | let hide; 218 | let promise; 219 | 220 | beforeEach(() => { 221 | hide = jasmine.createSpy('hide'); 222 | 223 | promise = runMeteorMethod({ 224 | spinner: { 225 | hide, 226 | show() {} 227 | } 228 | }); 229 | }); 230 | 231 | it('should hide spinner when resolved', (done) => { 232 | expect(spyCall).toHaveBeenCalled(); 233 | expect(hide).not.toHaveBeenCalled(); 234 | 235 | resolveMethod('test-resolved'); 236 | 237 | promise.finally(() => { 238 | expect(hide).toHaveBeenCalled(); 239 | }); 240 | promise.finally(done); 241 | }); 242 | 243 | it('should hide spinner when rejected', (done) => { 244 | expect(spyCall).toHaveBeenCalled(); 245 | expect(hide).not.toHaveBeenCalled(); 246 | 247 | rejectMethod('test-rejected'); 248 | 249 | promise.finally(() => { 250 | expect(hide).toHaveBeenCalled(); 251 | }); 252 | promise.finally(done); 253 | }); 254 | }); 255 | }); 256 | }; 257 | -------------------------------------------------------------------------------- /tests/state-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('@State()', () => { 3 | let target; 4 | const state = { 5 | name: 'test' 6 | }; 7 | // spies 8 | const spyLocation = {}; 9 | const spyUrlRouter = {}; 10 | const spyState = {}; 11 | 12 | /** 13 | * Call State on target and load ng module 14 | */ 15 | function doStateRaw(opts) { 16 | const result = angular2now.State(opts)(target); 17 | 18 | window.module(ngModuleName); 19 | inject(); 20 | 21 | return result; 22 | } 23 | 24 | /** 25 | * Same as doStateRaw but with predefined state options 26 | */ 27 | function doState(opts) { 28 | return doStateRaw(angular.merge(angular.copy(state), opts)); 29 | } 30 | 31 | /** 32 | * Get option's value from state definition object 33 | */ 34 | function getSDO(opt) { 35 | return spyState.state.calls.mostRecent().args[1][opt]; 36 | } 37 | 38 | /** 39 | * Use it if you want to set state's option 40 | * and you expect the same value on state definition object. 41 | */ 42 | function expectSDO(name, value) { 43 | doState({ 44 | [name]: value 45 | }); 46 | expect(getSDO(name)).toEqual(value); 47 | } 48 | 49 | beforeEach(() => { 50 | // reset target 51 | target = {}; 52 | // set spies 53 | angular.module('providersConfig', ['ui.router']) 54 | .config(function ($locationProvider, $urlRouterProvider, $stateProvider) { 55 | // spy on html5Mode 56 | spyLocation.html5Mode = spyOn($locationProvider, 'html5Mode'); 57 | // spy on otherwise 58 | spyUrlRouter.otherwise = spyOn($urlRouterProvider, 'otherwise'); 59 | // spy on state 60 | spyState.state = spyOn($stateProvider, 'state'); 61 | }); 62 | window.module('providersConfig'); 63 | angular2now.SetModule(`ns:${ngModuleName}`, ['ui.router']); 64 | }); 65 | 66 | describe('before target', () => { 67 | it('should fail on missing options', () => { 68 | expect(() => { 69 | angular2now.State(); 70 | }).toThrowError(Error, /options/); 71 | }); 72 | 73 | it('should fail on missing name option', () => { 74 | expect(() => { 75 | angular2now.State({ 76 | foo: 'bar' 77 | }); 78 | }).toThrowError(Error, /options/); 79 | }); 80 | 81 | it('should fail if argument is not an instance of Object', () => { 82 | expect(() => { 83 | angular2now.State(''); 84 | }).toThrowError(Error, /options/); 85 | }); 86 | }); 87 | 88 | describe('html5Mode', () => { 89 | it('should set html5Mode', () => { 90 | doState({ 91 | html5Mode: true 92 | }); 93 | expect(spyLocation.html5Mode).toHaveBeenCalledWith(true); 94 | }); 95 | }); 96 | 97 | describe('defaultRoute', () => { 98 | it('should set url option if true', () => { 99 | doState({ 100 | url: 'foo', 101 | defaultRoute: true 102 | }); 103 | expect(spyUrlRouter.otherwise).toHaveBeenCalledWith('foo'); 104 | }); 105 | 106 | it('should set defaultRoute option if string', () => { 107 | doState({ 108 | url: 'foo', 109 | defaultRoute: 'bar' 110 | }); 111 | expect(spyUrlRouter.otherwise).toHaveBeenCalledWith('bar'); 112 | }); 113 | }); 114 | 115 | describe('state', () => { 116 | describe('name', () => { 117 | it('should set the same name as in options', () => { 118 | doState(); 119 | expect(spyState.state).toHaveBeenCalledWith(state.name, jasmine.any(Object)); 120 | }); 121 | }); 122 | describe('url', () => { 123 | it('should be the same as in options', () => { 124 | expectSDO('url', 'foo'); 125 | }); 126 | }); 127 | 128 | describe('params', () => { 129 | it('should be the same as in options', () => { 130 | expectSDO('params', { 131 | foo: 'bar' 132 | }); 133 | }); 134 | }); 135 | 136 | describe('abstract', () => { 137 | it('should be the same as in options', () => { 138 | expectSDO('abstract', true); 139 | }); 140 | }); 141 | 142 | describe('template', () => { 143 | it('should be the same as in options', () => { 144 | expectSDO('template', ''); 145 | // 146 | expect(getSDO('templateUrl')).toBeUndefined(); 147 | expect(getSDO('templateProvider')).toBeUndefined(); 148 | }); 149 | 150 | it('should contain div with ui-view directive', () => { 151 | doState({}); 152 | expect(getSDO('template')).toContain('div'); 153 | expect(getSDO('template')).toContain('ui-view'); 154 | expect(getSDO('templateUrl')).toBeUndefined(); 155 | expect(getSDO('templateProvider')).toBeUndefined(); 156 | }); 157 | }); 158 | 159 | describe('templateUrl', () => { 160 | it('should be the same as in options', () => { 161 | expectSDO('templateUrl', 'foo.html'); 162 | // 163 | expect(getSDO('template')).toBeUndefined(); 164 | expect(getSDO('templateProvider')).toBeUndefined(); 165 | }); 166 | }); 167 | 168 | describe('templateProvider', () => { 169 | it('should be the same as in options', () => { 170 | expectSDO('templateProvider', function templateProvider() {}); 171 | // 172 | expect(getSDO('template')).toBeUndefined(); 173 | expect(getSDO('templateUrl')).toBeUndefined(); 174 | }); 175 | }); 176 | 177 | describe('onEnter', () => { 178 | it('should be the same as in options', () => { 179 | expectSDO('onEnter', function onEnter() {}); 180 | }); 181 | }); 182 | 183 | describe('onExit', () => { 184 | it('should be the same as in options', () => { 185 | expectSDO('onExit', function onExit() {}); 186 | }); 187 | }); 188 | 189 | describe('data', () => { 190 | it('should be the same as in options', () => { 191 | expectSDO('data', { 192 | foo: 'bar' 193 | }); 194 | }); 195 | }); 196 | 197 | describe('parent', () => { 198 | it('should be the same as in options', () => { 199 | expectSDO('parent', 'baz'); 200 | }); 201 | }); 202 | 203 | describe('controller', () => { 204 | it('should use target if no controller provided', () => { 205 | doState(); 206 | expect(getSDO('controller')).toBe(target); 207 | }); 208 | }); 209 | }); 210 | 211 | describe('resolve', () => { 212 | it('should remove namespace from services', () => { 213 | const resolve = jasmine.createSpyObj('resolve', ['foo', 'bar']); 214 | 215 | function controller() { 216 | } 217 | 218 | controller.$inject = ['ns_bar']; 219 | 220 | doState({ 221 | resolve, 222 | controller 223 | }); 224 | 225 | expect(getSDO('controller').$inject).toContain('bar'); 226 | }); 227 | }); 228 | }); 229 | }; 230 | -------------------------------------------------------------------------------- /src/api/state.js: -------------------------------------------------------------------------------- 1 | import { common } from './../common'; 2 | import { serviceExists, nameSpace, camelCase } from './../utils'; 3 | 4 | /** 5 | * State can be used to annotate either a Component or a class and assign 6 | * a ui-router state to it. 7 | * 8 | * @param options literal object 9 | * name: name of the state 10 | * url: url associated with this state 11 | * template: template 12 | * templateUrl: templateUrl 13 | * templateProvider: templateProvider 14 | * defaultRoute: truthy = .otherwise(url) 15 | * string = .otherwise(defaultRoute) 16 | * resolve: Literal object, see ui-router resolve 17 | * abstract: true/false 18 | * params: Literal object, see ui-router doco 19 | * parent: Define a custom parent state 20 | * controller: A controller is automatically assigned, but if you need 21 | * finer control then you can assign your own controller 22 | * controllerAs: Specify ControllerAs for cases when there is no 23 | * @Component used 24 | * 25 | * If a class is annotated then it is assumed to be the controller and 26 | * the state name will be used as the name of the injectable service 27 | * that will hold any resolves requested. 28 | * 29 | * When a component is annotated and resolves requested, then the component's 30 | * selector name is used as the name of the injectable service that holds 31 | * their values. 32 | */ 33 | export function State(options) { 34 | if (!options || !(options instanceof Object) || options.name === undefined) { 35 | throw new Error('@State: Valid options are: name, url, defaultRoute, template, templateUrl, templateProvider, resolve, abstract, parent, data.'); 36 | } 37 | 38 | return function StateTarget(target) { 39 | let deps; 40 | const resolvedServiceName = nameSpace(camelCase(target.selector || (options.name + '').replace('.', '-'))); 41 | 42 | // Indicates if there is anything to resolve 43 | let doResolve = false; 44 | 45 | // Values to resolve can either be supplied in options.resolve or as a static method on the 46 | // component's class 47 | const resolves = options.resolve || target.resolve; 48 | 49 | // Is there a resolve block? 50 | if (resolves && resolves instanceof Object) { 51 | deps = Object.keys(resolves); 52 | 53 | if (deps.length) { 54 | doResolve = true; 55 | } 56 | } 57 | 58 | // Create an injectable value service to share the resolved values with the controller 59 | // The service bears the same name as the component's camelCased selector name. 60 | if (doResolve) { 61 | if (!serviceExists(resolvedServiceName)) { 62 | angular.module(common.currentModule).value(resolvedServiceName, {}); 63 | } 64 | } 65 | 66 | // Configure the state 67 | angular.module(common.currentModule) 68 | .config(['$urlRouterProvider', '$stateProvider', '$locationProvider', 69 | function ($urlRouterProvider, $stateProvider, $locationProvider) { 70 | // Activate this state, if options.defaultRoute = true. 71 | // If you don't want this then don't set options.defaultRoute to true 72 | // and, instead, use $state.go inside the constructor to active a state. 73 | // You can also pass a string to defaultRoute, which will become the default route. 74 | if (options.defaultRoute) { 75 | $urlRouterProvider.otherwise((typeof options.defaultRoute === 'string') ? options.defaultRoute : options.url); 76 | } 77 | 78 | // Optionally configure html5Mode 79 | if (!(typeof options.html5Mode === 'undefined')) { 80 | $locationProvider.html5Mode(options.html5Mode); 81 | } 82 | 83 | // The user can supply a controller through a parameter in options 84 | // or the class itself can be used as the controller if no component is annotated. 85 | const userController = options.controller || (!target.selector ? target : undefined); 86 | 87 | // Also, de-namespace the resolve injectables for ui-router to inject correctly 88 | if (userController && userController.$inject && userController.$inject.length && deps && deps.length) { 89 | deps.forEach(function (dep) { 90 | const i = userController.$inject.indexOf(common.currentNameSpace + '_' + dep); 91 | 92 | if (i !== -1) { 93 | userController.$inject[i] = dep; 94 | } 95 | }); 96 | } 97 | 98 | 99 | // This is the state definition object 100 | const sdo = { 101 | url: options.url, 102 | 103 | // Default values for URL parameters can be configured here. 104 | // ALso, parameters that do not appear in the URL can be configured here. 105 | params: options.params, 106 | 107 | // The State applied to a bootstrap component can be abstract, 108 | // if you don't want that state to be able to activate. 109 | abstract: options.abstract, 110 | 111 | templateUrl: options.templateUrl, 112 | 113 | // This is the "inline" template, as opposed to the templateUrl. 114 | // 1) If either options.templateUrl or options.templateProvider is specified then 115 | // template will be set to undefined. 116 | // 2) If options.template is provided then it will be used. 117 | // 3) Otherwise, if this is a component, but not the bootstrap(**) component, 118 | // then we use it's selector to create the inline template "". 119 | // 4) Otherwise, we provide the following default template "
". 120 | // (**) The bootstrap component will be rendered by Angular directly and must not 121 | // be rendered again by ui-router, or you will literally see it twice. 122 | // todo: allow the user to specify their own div/span instead of forcing "div(ui-view)" 123 | template: (target.template || target.templateUrl) && !target.bootstrap && target.selector ? target.selector.replace(/^(.*)$/, '<$1>') : '
', 124 | 125 | // The option for dynamically setting a template based on local values 126 | // or injectable services 127 | templateProvider: options.templateProvider, 128 | 129 | // Do we need to resolve stuff? If so, then we also provide a controller to catch the resolved data. 130 | resolve: resolves, 131 | 132 | // A user supplied controller OR 133 | // An internally created proxy controller, if resolves were requested for a Component. 134 | controller: doResolve ? controller : undefined, 135 | 136 | // Optionally controllerAs can be specifically set for those situations, 137 | // when we use @State on a class and there is no @Component defined. 138 | controllerAs: common.ng2nOptions.hasOwnProperty('controllerAs') && !target.hasOwnProperty('selector') ? common.ng2nOptions.controllerAs : undefined, 139 | 140 | // onEnter and onExit events 141 | onEnter: options.onEnter, 142 | onExit: options.onExit, 143 | 144 | // Custom parent State 145 | parent: options.parent, 146 | 147 | // Custom data 148 | data: options.data 149 | }; 150 | 151 | // sdo's template 152 | if (options.templateUrl || options.templateProvider) { 153 | sdo.template = undefined; 154 | } else if (options.template) { 155 | sdo.template = options.template; 156 | } 157 | 158 | // sdo's controller 159 | if (userController) { 160 | sdo.controller = userController; 161 | } 162 | 163 | // sdo's controllerAs 164 | if (target.controllerAs) { 165 | sdo.controllerAs = target.controllerAs; 166 | } else if (options.controllerAs) { 167 | sdo.controllerAs = options.controllerAs; 168 | } 169 | 170 | // Create the state 171 | $stateProvider.state(options.name, sdo); 172 | 173 | // When our automatic controller is used, we inject the resolved values into it, 174 | // along with the injectable service that will be used to publish them. 175 | // If the user supplied a controller than we do not inject anything 176 | if (doResolve) { 177 | deps.unshift(resolvedServiceName); 178 | 179 | controller.$inject = deps; 180 | } 181 | 182 | // Populate the published service with the resolved values 183 | function controller(...args) { 184 | // This is the service that we "unshifted" earlier 185 | const localScope = args[0]; 186 | 187 | args = args.slice(1); 188 | 189 | // Now we copy the resolved values to the service. 190 | // This service can be injected into a component's constructor, for example. 191 | deps.slice(1).forEach((v, i) => { 192 | localScope[v] = args[i]; 193 | }); 194 | } 195 | } 196 | ]); 197 | return target; 198 | }; 199 | } 200 | -------------------------------------------------------------------------------- /src/api/component.js: -------------------------------------------------------------------------------- 1 | import { View } from './view'; 2 | import { Inject } from './inject'; 3 | import { common } from './../common'; 4 | import { camelCase, unCamelCase } from './../utils'; 5 | 6 | // function Directive(options) { 7 | // 8 | // // A string passed is assumed to be the attribute name of the directive. 9 | // if (typeof options === 'string') 10 | // options = { selector: options }; 11 | // 12 | // // Directives have shared scope by default (scope:undefined). 13 | // // Optionally they can have a new scope created (scope: true). 14 | // // If you require an isolate scope for your directive then 15 | // // pass "scope: { ... }" in options. 16 | // if (options && !options.hasOwnProperty('scope')) 17 | // angular.merge(options, { scope: undefined }); 18 | // 19 | // return Component(options); 20 | // } 21 | 22 | export function Component(options) { 23 | options = options || {}; 24 | // Allow shorthand notation of just passing the selector name as a string 25 | if (typeof options === 'string') { 26 | options = { 27 | selector: options 28 | }; 29 | } 30 | 31 | return function ComponentTarget(target) { 32 | let isClass = false; 33 | 34 | // Create a stub controller and substitute it for the target's constructor, 35 | // so that we can call the target's constructor later, within the link function. 36 | target = deferController(target, controller); 37 | 38 | // service injections, which could also have been specified by using @Inject 39 | if (options.injectables && options.injectables instanceof Array) { 40 | target = Inject(options.injectables)(target); 41 | } 42 | // injectables has been renamed to services 43 | if (options.services && options.services instanceof Array) { 44 | target = Inject(options.services)(target); 45 | } 46 | // injectables has been renamed to providers, actually, but also keeping 47 | // services in case anyone has used it already. 48 | if (options.providers && options.providers instanceof Array) { 49 | target = Inject(options.providers)(target); 50 | } 51 | 52 | // Selector name may be prefixed with a '.', in which case "restrict: 'C'" will be used 53 | options.selector = camelCase(options.selector || '') + ''; 54 | if (options.selector[0] === '.') { 55 | isClass = true; 56 | options.selector = options.selector.slice(1); 57 | } 58 | // Save the unCamelCased selector name, so that bootstrap() can use it 59 | target.selector = unCamelCase(options.selector); 60 | 61 | // template options can be set with Component or with View 62 | // so, we run View on the passed in options first. 63 | if (options.template || options.templateUrl || options.transclude || options.directives) { 64 | View(options)(target); 65 | } 66 | 67 | // The template(Url) can also be passed in from the @View decorator 68 | options.template = target.template || undefined; 69 | options.templateUrl = target.templateUrl || undefined; 70 | 71 | // Build the require array. 72 | // Our controller needs the same injections as the component's controller, 73 | // but with the "@*" injections renamed to "$scope". The link function will pass 74 | // the "@*" injections directly to the component controller. 75 | const requiredControllers = [options.selector]; 76 | 77 | target.$inject = target.$inject || []; 78 | target.$inject = target.$inject.map((dep) => { 79 | if (/^@[^]{0,2}/.test(dep[0])) { 80 | requiredControllers.push('?' + dep.slice(1)); 81 | dep = 'delete-me'; 82 | } 83 | return dep; 84 | }); 85 | 86 | // Remove all the 'delete-me' entries 87 | target.$inject = target.$inject.filter((v) => v !== 'delete-me'); 88 | 89 | if (target.meteorReactive) { 90 | // Prepend angular-meteor injectables 91 | target.$inject.unshift('$scope'); 92 | target.$inject.unshift('$reactive'); 93 | } 94 | 95 | // Remember the original $inject, as it will be needed in the link function. 96 | // In the link function we will receive any requested component controllers 97 | // which we will then inject into the arguments that we will pass to the 98 | // actual constructor of our component. 99 | target.$injectDefer = target.$inject || []; 100 | 101 | // Create the angular directive 102 | const ddo = { 103 | controllerAs: options.controllerAs || common.controllerAs || target.controllerAs || options.selector, 104 | bindToController: typeof target.bindToController === 'boolean' ? target.bindToController : true, 105 | restrict: (options.template + options.templateUrl) ? 'EA' : isClass ? 'C' : 'A', 106 | scope: {}, 107 | template: options.template, 108 | templateUrl: options.templateUrl, 109 | controller: target, 110 | replace: options.replace || false, 111 | transclude: /ng-transclude/i.test(options.template) || target.transclude, 112 | require: options.require || target.require || requiredControllers, 113 | link: options.link || target.link || link 114 | }; 115 | 116 | // ddo's restrict 117 | if (options.restrict) { 118 | ddo.restrict = options.restrict; 119 | } 120 | // ddo's scope 121 | if (target.hasOwnProperty('scope')) { 122 | ddo.scope = target.scope; 123 | } else if (options.hasOwnProperty('scope')) { 124 | ddo.scope = options.scope; 125 | } else if (options['bind']) { 126 | ddo.scope = options['bind']; 127 | } 128 | 129 | 130 | try { 131 | angular.module(common.currentModule) 132 | .directive(options.selector, () => ddo); 133 | } catch (er) { 134 | throw new Error('Does module "' + common.currentModule + '" exist? You may need to use SetModule("youModuleName").'); 135 | } 136 | 137 | return target; 138 | 139 | // The stub controller below saves injected objects, so we can re-inject them 140 | // into the "real" controller when the link function executes. 141 | // This allows me to add stuff to the controller and it's "this", which is required 142 | // for some future functionality. 143 | function controller(...args) { 144 | const ctrlInstance = this; 145 | let toInjectAfter = []; 146 | let injectedDeps = args; 147 | 148 | if (target.meteorReactive) { 149 | // Get injected angular-meteor objects 150 | const $reactive = injectedDeps[0]; 151 | const $scope = injectedDeps[1]; 152 | 153 | $reactive(ctrlInstance).attach($scope); 154 | 155 | toInjectAfter = injectedDeps.slice(0, 2); 156 | injectedDeps = injectedDeps.slice(2); 157 | target.$inject = target.$inject.slice(2); 158 | } 159 | if (target.localInjectables) { 160 | target.$inject.forEach((value, index) => { 161 | ctrlInstance[value] = injectedDeps[index]; 162 | }); 163 | } 164 | // Call the original constructor, which is now called $$init, injecting all the 165 | // dependencies requested. 166 | this.$$init.apply(this, injectedDeps); 167 | 168 | if (toInjectAfter.length > 0) { 169 | target.$inject = ['$reactive', '$scope'].concat(target.$inject); 170 | injectedDeps.unshift(toInjectAfter[1]); 171 | injectedDeps.unshift(toInjectAfter[0]); 172 | } 173 | } 174 | // This function allows me to replace a component's "real" constructor with my own. 175 | // I do this, because I want to decorate the $scope and this before instantiating 176 | // the class's original controller. Also, this enables me to inject 177 | // other component's controllers into the constructor, the same way as you would 178 | // inject a service. 179 | // The component's original constructor is assigned to the init method of the 180 | // component's class, so that when it executes it will run in the original scope and 181 | // closures that it was defined in. It is the init method that is called within the 182 | // link function. 183 | function deferController(target, controller) { 184 | // Save the original prototype 185 | const oldproto = target.prototype; 186 | // Save the original constructor, so we can call it later 187 | const construct = target.prototype.constructor; 188 | // Save any static properties 189 | const staticProps = {}; 190 | 191 | for (const i in target) { 192 | staticProps[i] = target[i]; 193 | } 194 | // Assign a new constructor, which holds the injected deps. 195 | target = controller; 196 | // Restore the original prototype 197 | target.prototype = oldproto; 198 | // Restore saved static properties 199 | for (const i in staticProps) { 200 | target[i] = staticProps[i]; 201 | } 202 | // Store the original constructor under the name $$init, 203 | // which we will call in the link function. 204 | target.prototype.$$init = construct; 205 | // Hide $$init from the user's casual inspections of the controller 206 | // Object.defineProperty(target.prototype, "$$init", {enumerable: false}) 207 | return target; 208 | } 209 | 210 | function link(scope, el, attr, controllers) { 211 | // Create a service with the same name as the selector 212 | // That holds a reference to our component 213 | // angular.module(currentModule).value(camelCase(target.selector), controllers[0]); 214 | 215 | // Alternate syntax for the injection of other component's controllers 216 | if (controllers[0].$dependson) { 217 | controllers[0].$dependson.apply(controllers[0], controllers.slice(1)); 218 | } 219 | } 220 | }; 221 | } 222 | -------------------------------------------------------------------------------- /dist/angular2-now.min.js: -------------------------------------------------------------------------------- 1 | /*! angular2-now v1.1.6 */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.angular2now=t():e.angular2now=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(){o.common.isCordova="undefined"!=typeof cordova,o.common.angularModule=angular.module}Object.defineProperty(t,"__esModule",{value:!0});var o=n(1),c=n(2),l=n(3),a=n(7),u=n(8),i=n(4),s=n(5),p=n(9),d=n(10),m=n(11),f=n(12),v=n(13),g=n(14),y=n(15),j=n(16),M=n(17),h={init:r,SetModule:c.SetModule,Component:l.Component,ScopeShared:a.ScopeShared,ScopeNew:u.ScopeNew,View:i.View,Inject:s.Inject,Controller:p.Controller,Service:d.Service,Filter:m.Filter,bootstrap:f.bootstrap,State:v.State,options:g.options,Options:g.Options,MeteorMethod:y.MeteorMethod,MeteorReactive:j.MeteorReactive,LocalInjectables:M.LocalInjectables,Directive:l.Component,Injectable:d.Service};"undefined"==typeof Meteor&&r(),"undefined"!=typeof window&&(window.angular2now=h),t["default"]=h,e.exports=t["default"]},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n={angularModule:void 0,currentModule:void 0,currentNameSpace:void 0,isCordova:!1,ng2nOptions:{currentModule:function(){return n.currentModule}},controllerAs:void 0,$q:angular.injector(["ng"]).get("$q")};t.common=n},function(e,t,n){"use strict";function r(){return o.common.currentModule=arguments[0].split(":"),1===o.common.currentModule.length?o.common.currentModule=o.common.currentModule[0]:(o.common.currentNameSpace=o.common.currentModule[0],o.common.currentModule=o.common.currentModule[1],arguments[0]=o.common.currentModule),o.common.angularModule.apply(angular,arguments)}Object.defineProperty(t,"__esModule",{value:!0}),t.SetModule=r;var o=n(1)},function(e,t,n){"use strict";function r(e){return e=e||{},"string"==typeof e&&(e={selector:e}),function(t){function n(){for(var e=this,n=[],r=arguments.length,o=Array(r),c=0;r>c;c++)o[c]=arguments[c];var l=o;if(t.meteorReactive){var a=l[0],u=l[1];a(e).attach(u),n=l.slice(0,2),l=l.slice(2),t.$inject=t.$inject.slice(2)}t.localInjectables&&t.$inject.forEach(function(t,n){e[t]=l[n]}),this.$$init.apply(this,l),n.length>0&&(t.$inject=["$reactive","$scope"].concat(t.$inject),l.unshift(n[1]),l.unshift(n[0]))}function r(e,t){var n=e.prototype,r=e.prototype.constructor,o={};for(var c in e)o[c]=e[c];e=t,e.prototype=n;for(var c in o)e[c]=o[c];return e.prototype.$$init=r,e}function u(e,t,n,r){r[0].$dependson&&r[0].$dependson.apply(r[0],r.slice(1))}var i=!1;t=r(t,n),e.injectables&&e.injectables instanceof Array&&(t=(0,c.Inject)(e.injectables)(t)),e.services&&e.services instanceof Array&&(t=(0,c.Inject)(e.services)(t)),e.providers&&e.providers instanceof Array&&(t=(0,c.Inject)(e.providers)(t)),e.selector=(0,a.camelCase)(e.selector||"")+"","."===e.selector[0]&&(i=!0,e.selector=e.selector.slice(1)),t.selector=(0,a.unCamelCase)(e.selector),(e.template||e.templateUrl||e.transclude||e.directives)&&(0,o.View)(e)(t),e.template=t.template||void 0,e.templateUrl=t.templateUrl||void 0;var s=[e.selector];t.$inject=t.$inject||[],t.$inject=t.$inject.map(function(e){return/^@[^]{0,2}/.test(e[0])&&(s.push("?"+e.slice(1)),e="delete-me"),e}),t.$inject=t.$inject.filter(function(e){return"delete-me"!==e}),t.meteorReactive&&(t.$inject.unshift("$scope"),t.$inject.unshift("$reactive")),t.$injectDefer=t.$inject||[];var p={controllerAs:e.controllerAs||l.common.controllerAs||t.controllerAs||e.selector,bindToController:"boolean"==typeof t.bindToController?t.bindToController:!0,restrict:e.template+e.templateUrl?"EA":i?"C":"A",scope:{},template:e.template,templateUrl:e.templateUrl,controller:t,replace:e.replace||!1,transclude:/ng-transclude/i.test(e.template)||t.transclude,require:e.require||t.require||s,link:e.link||t.link||u};e.restrict&&(p.restrict=e.restrict),t.hasOwnProperty("scope")?p.scope=t.scope:e.hasOwnProperty("scope")?p.scope=e.scope:e.bind&&(p.scope=e.bind);try{angular.module(l.common.currentModule).directive(e.selector,function(){return p})}catch(d){throw new Error('Does module "'+l.common.currentModule+'" exist? You may need to use SetModule("youModuleName").')}return t}}Object.defineProperty(t,"__esModule",{value:!0}),t.Component=r;var o=n(4),c=n(5),l=n(1),a=n(6)},function(e,t){"use strict";function n(e){function t(e){var t=(e||"").match(/\]([^\>]+)/i);return t&&-1===t[1].toLowerCase().indexOf("ng-transclude")&&(e=e.replace(/\r;r++)n[r]=arguments[r];if(e=n[0]instanceof Array?n[0]:n,0===e.length)throw new Error("@Inject: No dependencies passed in");return function(t,n,r){var c=t;if(r&&(c=r.value),!c)throw new TypeError("@Inject can only be used with classes or class methods.");var l=c.$inject;return c.$inject=[],e.forEach(function(e){"$"!==e[0]&&"@"!==e[0]&&-1===e.indexOf("_")&&(e=(0,o.nameSpace)(e)),-1===c.$inject.indexOf(e)&&c.$inject.push(e)}),l&&(c.$inject=c.$inject.concat(l)),r||t}}Object.defineProperty(t,"__esModule",{value:!0}),t.Inject=r;var o=n(6)},function(e,t,n){"use strict";function r(e){return u.common.currentNameSpace?u.common.currentNameSpace+"_"+e:e}function o(e,t){return angular.module(t||u.common.currentModule)._invokeQueue.filter(function(t){return"$provide"===t[0]&&t[2][0]===e})[0]}function c(e){return!!o(e)}function l(e){return e.replace(/-(.)/g,function(e,t){return t.toUpperCase()})}function a(e){var t=e.replace(/([A-Z])/g,"-$1").replace(/--/g,"-").toLowerCase();return"-"===t[0]?t.slice(1):t}Object.defineProperty(t,"__esModule",{value:!0}),t.nameSpace=r,t.getService=o,t.serviceExists=c,t.camelCase=l,t.unCamelCase=a;var u=n(1)},function(e,t){"use strict";function n(e){return e.scope=void 0,e}Object.defineProperty(t,"__esModule",{value:!0}),t.ScopeShared=n},function(e,t){"use strict";function n(e){return e.scope=!0,e}Object.defineProperty(t,"__esModule",{value:!0}),t.ScopeNew=n},function(e,t,n){"use strict";function r(e){return e=e||{},"string"==typeof e&&(e={name:e}),function(t){return angular.module(o.common.currentModule).controller((0,c.nameSpace)(e.name),t),t}}Object.defineProperty(t,"__esModule",{value:!0}),t.Controller=r;var o=n(1),c=n(6)},function(e,t,n){"use strict";function r(e){return e=e||{},"string"==typeof e&&(e={name:e}),function(t){return angular.module(o.common.currentModule).service((0,c.nameSpace)(e.name),t),t}}Object.defineProperty(t,"__esModule",{value:!0}),t.Service=r;var o=n(1),c=n(6)},function(e,t,n){"use strict";function r(e){return e=e||{},"string"==typeof e&&(e={name:e}),function(t){function n(){var e=Array.prototype.slice.call(arguments),n=new(Function.prototype.bind.apply(t,[null].concat(e)));return n}return n.$inject=t.$inject,angular.module(o.common.currentModule).filter((0,c.nameSpace)(e.name),n),t}}Object.defineProperty(t,"__esModule",{value:!0}),t.Filter=r;var o=n(1),c=n(6)},function(e,t,n){"use strict";function r(e,t){function n(){var n=void 0;n=r?document.body:document.querySelector(e.selector),angular.bootstrap(n,[c],t)}var r=!1;(!e||e&&!e.selector&&"function"==typeof e)&&(e={selector:o.common.currentModule},r=!0),"string"==typeof e&&(e={selector:e}),e.bootstrap=!0;var c=e.selector||o.common.currentModule;c!==o.common.currentModule&&angular.module(c),t||(t={strictDi:!1}),o.common.isCordova?angular.element(document).on("deviceready",n):angular.element(document).ready(n)}Object.defineProperty(t,"__esModule",{value:!0}),t.bootstrap=r;var o=n(1)},function(e,t,n){"use strict";function r(e){if(!(e&&e instanceof Object&&void 0!==e.name))throw new Error("@State: Valid options are: name, url, defaultRoute, template, templateUrl, templateProvider, resolve, abstract, parent, data.");return function(t){var n=void 0,r=(0,c.nameSpace)((0,c.camelCase)(t.selector||(e.name+"").replace(".","-"))),l=!1,a=e.resolve||t.resolve;return a&&a instanceof Object&&(n=Object.keys(a),n.length&&(l=!0)),l&&((0,c.serviceExists)(r)||angular.module(o.common.currentModule).value(r,{})),angular.module(o.common.currentModule).config(["$urlRouterProvider","$stateProvider","$locationProvider",function(c,u,i){function s(){for(var e=arguments.length,t=Array(e),r=0;e>r;r++)t[r]=arguments[r];var o=t[0];t=t.slice(1),n.slice(1).forEach(function(e,n){o[e]=t[n]})}e.defaultRoute&&c.otherwise("string"==typeof e.defaultRoute?e.defaultRoute:e.url),"undefined"!=typeof e.html5Mode&&i.html5Mode(e.html5Mode);var p=e.controller||(t.selector?void 0:t);p&&p.$inject&&p.$inject.length&&n&&n.length&&n.forEach(function(e){var t=p.$inject.indexOf(o.common.currentNameSpace+"_"+e);-1!==t&&(p.$inject[t]=e)});var d={url:e.url,params:e.params,"abstract":e["abstract"],templateUrl:e.templateUrl,template:(t.template||t.templateUrl)&&!t.bootstrap&&t.selector?t.selector.replace(/^(.*)$/,"<$1>"):'
',templateProvider:e.templateProvider,resolve:a,controller:l?s:void 0,controllerAs:o.common.ng2nOptions.hasOwnProperty("controllerAs")&&!t.hasOwnProperty("selector")?o.common.ng2nOptions.controllerAs:void 0,onEnter:e.onEnter,onExit:e.onExit,parent:e.parent,data:e.data};e.templateUrl||e.templateProvider?d.template=void 0:e.template&&(d.template=e.template),p&&(d.controller=p),t.controllerAs?d.controllerAs=t.controllerAs:e.controllerAs&&(d.controllerAs=e.controllerAs),u.state(e.name,d),l&&(n.unshift(r),s.$inject=n)}]),t}}Object.defineProperty(t,"__esModule",{value:!0}),t.State=r;var o=n(1),c=n(6)},function(e,t,n){"use strict";function r(e){return e?("undefined"!=typeof e.controllerAs&&(c.common.controllerAs=e.controllerAs),c.common.ng2nOptions.spinner=e.spinner||{show:angular.noop,hide:angular.noop},c.common.ng2nOptions.events=e.events||{beforeCall:angular.noop,afterCall:angular.noop},void("undefined"!=typeof e.noConflict&&(angular.module=e.noConflict?c.common.angularModule:l.SetModule))):c.common.ng2nOptions}function o(e){return function(t){return angular.merge(c.common.ng2nOptions,e),t}}Object.defineProperty(t,"__esModule",{value:!0}),t.options=r,t.Options=o;var c=n(1),l=n(2)},function(e,t,n){"use strict";function r(e){var t=angular.merge({},o.common.ng2nOptions,e),n=t.spinner||{show:angular.noop,hide:angular.noop},r=t.events||{beforeCall:angular.noop,afterCall:angular.noop};return function(e,c,l){return l.value=function(){function e(e,t){e?a.reject(e):a.resolve(t)}var l=Array.prototype.slice.call(arguments),a=o.common.$q.defer();if("string"==typeof n){if(!angular.injector(["ng",o.common.currentModule]).has(t.spinner))throw new Error('Spinner "'+n+'" does not exist.');n=angular.injector(["ng",o.common.currentModule]).get(t.spinner),t.spinner=n}return l.unshift(c),l.push(e),n&&n.show(),r.beforeCall&&r.beforeCall(),Meteor.call.apply(this,l),a.promise["finally"](function(){n.hide(),r.afterCall&&r.afterCall()}),a.promise},l}}Object.defineProperty(t,"__esModule",{value:!0}),t.MeteorMethod=r;var o=n(1)},function(e,t){"use strict";function n(e){return e.meteorReactive=!0,e}Object.defineProperty(t,"__esModule",{value:!0}),t.MeteorReactive=n},function(e,t){"use strict";function n(e){return e.localInjectables=!0,e}Object.defineProperty(t,"__esModule",{value:!0}),t.LocalInjectables=n}])}); 3 | //# sourceMappingURL=angular2-now.min.js.map -------------------------------------------------------------------------------- /tests/component-spec.js: -------------------------------------------------------------------------------- 1 | export default (angular2now, ngModuleName) => { 2 | describe('@Component()', () => { 3 | /** 4 | * Mock angular.module() 5 | * @type {Object} 6 | */ 7 | const moduleMock = { 8 | directive: () => {} 9 | }; 10 | /** 11 | * Target used in all tests 12 | */ 13 | let target; 14 | /** 15 | * Spy on angular.module 16 | */ 17 | let spyModule; 18 | /** 19 | * spy on angular.module().directive 20 | */ 21 | let spyDirective; 22 | /** 23 | * Names 24 | */ 25 | const nameCamel = 'testComponent'; 26 | const nameClass = '.testComponent'; 27 | const nameDashed = 'test-component'; 28 | 29 | /** 30 | * Returns directive's object 31 | * @param {Object|String} opt Component(opt) 32 | * @return {Object} target reference 33 | */ 34 | function getDDO(opt) { 35 | const ddo = spyDirective.calls.mostRecent().args[1](); 36 | 37 | return ddo[opt]; 38 | } 39 | 40 | /** 41 | * Retuns directive's name 42 | * @return {[type]} [description] 43 | */ 44 | function getDDName() { 45 | return spyDirective.calls.mostRecent().args[0]; 46 | } 47 | 48 | /** 49 | * Shorthand for angular2now.Component(opts)(target) 50 | * @param {Object|String} opts Component(opts) 51 | * @return {Object} target reference 52 | */ 53 | function doComponent(opts) { 54 | return angular2now.Component(opts)(target); 55 | } 56 | 57 | /** 58 | * Use it if you want to set component's option 59 | * and you expect the same value on directive definition object. 60 | * @param {String} name option's name 61 | * @param {Any} value option's value 62 | */ 63 | function expectDDO(name, value) { 64 | doComponent({ 65 | [name]: value 66 | }); 67 | expect(getDDO(name)).toEqual(value); 68 | } 69 | 70 | beforeEach(() => { 71 | // reset target 72 | target = function target() {}; 73 | // add spy on angular.module and return mock 74 | spyModule = spyOn(angular, 'module').and.returnValue(moduleMock); 75 | // add spy on angular.module().directive; 76 | spyDirective = spyOn(moduleMock, 'directive'); 77 | }); 78 | 79 | it('should have target at controller', () => { 80 | const result = doComponent(nameDashed); 81 | 82 | expect(getDDO('controller')).toBe(result); 83 | }); 84 | 85 | describe('options.selector', () => { 86 | it('should set selector if argument is a string', () => { 87 | const result = doComponent(nameDashed); 88 | 89 | expect(result.selector).toBe(nameDashed); 90 | expect(getDDName()).toBe(nameCamel); 91 | }); 92 | 93 | it('should be able to unCamelCase selector', () => { 94 | const result = doComponent(nameCamel); 95 | 96 | expect(result.selector).toBe(nameDashed); 97 | expect(getDDName()).toBe(nameCamel); 98 | }); 99 | 100 | it('should handle class name as selector', () => { 101 | const result = doComponent(nameClass); 102 | 103 | expect(result.selector).toBe(nameDashed); 104 | expect(getDDName()).toBe(nameCamel); 105 | }); 106 | 107 | it('should call angular.directive with proper selector', () => { 108 | const result = doComponent(nameClass); 109 | 110 | expect(result.selector).toBe(nameDashed); 111 | expect(spyDirective).toHaveBeenCalled(); 112 | expect(getDDName()).toBe(nameCamel); 113 | }); 114 | }); 115 | 116 | describe('options.injectables', () => { 117 | const injectables = ['$http', '$q']; 118 | 119 | it('should set injectables', () => { 120 | const result = doComponent({ 121 | injectables 122 | }); 123 | 124 | expect(result.$inject).toEqual(injectables); 125 | expect(result.$injectDefer).toEqual(result.$inject); 126 | }); 127 | }); 128 | 129 | describe('options.services', () => { 130 | const services = ['$http', '$q']; 131 | 132 | it('should set services', () => { 133 | const result = doComponent({ 134 | services 135 | }); 136 | 137 | expect(result.$inject).toEqual(services); 138 | expect(result.$injectDefer).toEqual(result.$inject); 139 | }); 140 | }); 141 | 142 | describe('options.providers', () => { 143 | const providers = ['$http', '$q']; 144 | 145 | it('should set providers', () => { 146 | const result = doComponent({ 147 | providers 148 | }); 149 | 150 | expect(result.$inject).toEqual(providers); 151 | expect(result.$injectDefer).toEqual(result.$inject); 152 | }); 153 | }); 154 | 155 | describe('options.template', () => { 156 | it('should set template', () => { 157 | expectDDO('template', 'foo'); 158 | }); 159 | }); 160 | 161 | describe('options.templateUrl', () => { 162 | it('should set templateUrl', () => { 163 | expectDDO('templateUrl', 'foo.html'); 164 | }); 165 | }); 166 | 167 | describe('options.transclude', () => { 168 | it('should set transclude to true', () => { 169 | expectDDO('transclude', true); 170 | }); 171 | it('should set not set transclude', () => { 172 | const transclude = false; 173 | 174 | doComponent({ 175 | transclude 176 | }); 177 | 178 | expect(getDDO('transclude')).toBeUndefined(); 179 | }); 180 | }); 181 | 182 | describe('options.restrict', () => { 183 | it('should set each restriction', () => { 184 | const restrictions = ['E', 'A', 'C', 'EA', 'EAC']; 185 | 186 | restrictions.forEach((restrict) => { 187 | expectDDO('restrict', restrict); 188 | }); 189 | }); 190 | }); 191 | 192 | describe('options.controllerAs', () => { 193 | it('should set controllerAs', () => { 194 | expectDDO('controllerAs', 'foo'); 195 | }); 196 | 197 | it('should overwrite target\'s controllerAs', () => { 198 | const controllerAs = 'foo'; 199 | 200 | target.controllerAs = 'bar'; 201 | doComponent({ 202 | controllerAs 203 | }); 204 | 205 | expect(getDDO('controllerAs')).toBe(controllerAs); 206 | }); 207 | }); 208 | 209 | describe('options.scope', () => { 210 | it('should set scope', () => { 211 | const scopes = [true, false, undefined, { 212 | foo: 'bar' 213 | }]; 214 | 215 | scopes.forEach((scope) => { 216 | expectDDO('scope', scope); 217 | }); 218 | }); 219 | 220 | it('should be skipped if target\'s scope is available', () => { 221 | const scopes = [true, false, undefined, { 222 | foo: 'bar' 223 | }]; 224 | 225 | scopes.forEach((scope) => { 226 | target.scope = scope; 227 | doComponent({ 228 | scope: { 229 | foo: 'baz' 230 | } 231 | }); 232 | 233 | expect(getDDO('scope')).toBe(scope); 234 | }); 235 | }); 236 | 237 | it('should be an empty object if neither of bind, scope or target\'s scope is available', () => { 238 | doComponent({ 239 | selector: nameDashed 240 | }); 241 | 242 | expect(getDDO('scope')).toEqual({}); 243 | }); 244 | }); 245 | 246 | describe('target.bindToController', () => { 247 | it('shoud set bindToController', () => { 248 | const bools = [true, false]; 249 | 250 | bools.forEach((val) => { 251 | target.bindToController = val; 252 | doComponent(); 253 | 254 | expect(getDDO('bindToController')).toBe(val); 255 | }); 256 | }); 257 | }); 258 | 259 | describe('target.require', () => { 260 | it('should set require', () => { 261 | const require = ['@foo']; 262 | 263 | target.require = require; 264 | doComponent(); 265 | 266 | expect(getDDO('require')).toBe(require); 267 | }); 268 | }); 269 | 270 | describe('options.require', () => { 271 | it('should set require', () => { 272 | expectDDO('require', ['@foo']); 273 | }); 274 | 275 | it('should overwrite target\'s require', () => { 276 | const require = ['@foo']; 277 | 278 | target.require = ['@bar']; 279 | doComponent({ 280 | require 281 | }); 282 | 283 | expect(getDDO('require')).toBe(require); 284 | }); 285 | }); 286 | 287 | describe('require', () => { 288 | // injectables prefixed with @ 289 | const atInjects = ['@^foo', '@bar']; 290 | // mix of injetables 291 | const injectables = ['$http'].concat(atInjects); 292 | // injectables used in controller 293 | const questionInjects = atInjects.map((inj) => '?' + inj.slice(1)); 294 | 295 | it('should set require with @ prefixed injectables', () => { 296 | doComponent({ 297 | injectables, 298 | selector: nameDashed 299 | }); 300 | 301 | const requireDDO = getDDO('require'); 302 | 303 | questionInjects.forEach((inj) => { 304 | expect(requireDDO.indexOf(inj)).not.toBe(-1); 305 | }); 306 | }); 307 | 308 | it('should set require with target', () => { 309 | doComponent({ 310 | injectables, 311 | selector: nameDashed 312 | }); 313 | 314 | expect(getDDO('require').indexOf(nameCamel)).not.toBe(-1); 315 | }); 316 | 317 | it('should remove transformed injectables', () => { 318 | const result = doComponent({ 319 | injectables, 320 | selector: nameDashed 321 | }); 322 | 323 | atInjects.forEach((inj) => { 324 | expect(result.$inject.indexOf(inj)).toBe(-1); 325 | expect(result.$injectDefer.indexOf(inj)).toBe(-1); 326 | }); 327 | }); 328 | }); 329 | 330 | describe('target.link', () => { 331 | it('should set link', () => { 332 | const link = function linkTarget() {}; 333 | 334 | target.link = link; 335 | doComponent(); 336 | 337 | expect(getDDO('link')).toBe(link); 338 | }); 339 | }); 340 | 341 | describe('options.link', () => { 342 | it('should set link', () => { 343 | expectDDO('link', function linkOptions() {}); 344 | }); 345 | 346 | it('should overwrite target\'s link', () => { 347 | const link = function linkOptions() {}; 348 | 349 | target.link = function linkTarget() {}; 350 | doComponent({ 351 | link 352 | }); 353 | 354 | expect(getDDO('link')).toBe(link); 355 | }); 356 | }); 357 | 358 | describe('restrict', () => { 359 | it('should be A by default', () => { 360 | doComponent(nameCamel); 361 | expect(getDDO('restrict')).toBe('A'); 362 | }); 363 | 364 | it('should be EA when templateUrl and non class selector', () => { 365 | doComponent({ 366 | selector: nameCamel, 367 | templateUrl: 'foo.html' 368 | }); 369 | expect(getDDO('restrict')).toBe('EA'); 370 | }); 371 | 372 | it('should be EA when template and non class selector', () => { 373 | doComponent({ 374 | selector: nameCamel, 375 | template: 'foo' 376 | }); 377 | expect(getDDO('restrict')).toBe('EA'); 378 | }); 379 | 380 | it('should be C when slector is a class name', () => { 381 | doComponent({ 382 | selector: nameClass 383 | }); 384 | expect(getDDO('restrict')).toBe('C'); 385 | }); 386 | }); 387 | 388 | describe('controllerAs', () => { 389 | it('should be the same as selector by default', () => { 390 | doComponent({ 391 | selector: nameDashed 392 | }); 393 | expect(getDDO('controllerAs')).toBe(nameCamel); 394 | }); 395 | 396 | it('should be owerwritten by options.controllerAs', () => { 397 | angular2now.options({ 398 | controllerAs: 'vm' 399 | }); 400 | doComponent({ 401 | selector: nameDashed 402 | }); 403 | expect(getDDO('controllerAs')).toBe('vm'); 404 | 405 | // reset 406 | angular2now.options({ 407 | controllerAs: null 408 | }); 409 | }); 410 | }); 411 | 412 | describe('options.bind', () => { 413 | it('should set scope', () => { 414 | const bind = { 415 | foo: 'bar' 416 | }; 417 | 418 | doComponent({ 419 | bind 420 | }); 421 | 422 | expect(getDDO('scope')).toEqual(bind); 423 | }); 424 | }); 425 | 426 | describe('transclude', () => { 427 | it('should be undefined by default', () => { 428 | doComponent({ 429 | selector: nameDashed 430 | }); 431 | 432 | expect(getDDO('transclude')).toBeUndefined(); 433 | }); 434 | 435 | it('should set transclude when template contains content element', () => { 436 | const template = ` 437 |
438 | 439 |
440 | `; 441 | 442 | doComponent({ 443 | template 444 | }); 445 | 446 | expect(getDDO('transclude')).toBe(true); 447 | }); 448 | 449 | it('should set transclude when template does not contain content', () => { 450 | const template = `
`; 451 | 452 | doComponent({ 453 | template 454 | }); 455 | 456 | expect(getDDO('transclude')).toBeUndefined(); 457 | }); 458 | }); 459 | 460 | describe('link', () => { 461 | it('should apply controllers on $dependson', () => { 462 | doComponent(); 463 | const link = getDDO('link'); 464 | const controllers = [ 465 | jasmine.createSpyObj('foo', ['$dependson']), { 466 | name: 'bar' 467 | }, { 468 | name: 'baz' 469 | } 470 | ]; 471 | 472 | expect(link).toEqual(jasmine.any(Function)); 473 | 474 | // simulate link execution 475 | link(null, null, null, controllers); 476 | 477 | // check arguments 478 | expect(controllers[0].$dependson).toHaveBeenCalledWith(controllers[1], controllers[2]); 479 | // check context 480 | expect(controllers[0].$dependson.calls.mostRecent().object).toBe(controllers[0]); 481 | }); 482 | }); 483 | }); 484 | }; 485 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular2-now [![Build Status](https://travis-ci.org/pbastowski/angular2-now.svg?branch=master)](https://travis-ci.org/pbastowski/angular2-now) [![Coverage Status](https://coveralls.io/repos/pbastowski/angular2-now/badge.svg?branch=master&service=github)](https://coveralls.io/github/pbastowski/angular2-now?branch=master) 2 | 3 | ## Angular 2.0 component syntax for Angular 1 apps 4 | 5 | Angular2-now gives you the ability to start coding your Angular 1.4+ apps using Angular 2 component syntax. You get to keep your investment in Angular 1 while learning some Angular 2 concepts. 6 | 7 | So, if you like the clean syntax of Angular 2, but are not yet ready or able to commit to it, then this library might just be what you're looking for. 8 | 9 | > **Meteor note**: Meteor package version 1.1.0 of angular2-now works with Meteor 1.2 or higher (repo branch `master`). The latest Meteor 1.1 package version is 0.3.18 (repo branch `meteor1.1`). 10 | 11 | ## If you are going to use Meteor 1.3... 12 | ... then you can install angular2-now with NPM, like this: 13 | 14 | npm install angular2-now 15 | 16 | You will need to import from `angular2-now` instead of `angular2now`. See the extra "-"? Here is an example: 17 | 18 | ````js 19 | import { Component } from "angular2-now" 20 | ```` 21 | 22 | > Temporarily, with Meteor 1.3 you need to do what's shown below, before you call `SetModule()`. This will be fixed in an upcoming release. 23 | 24 | ```js 25 | import { init } from "angular2-now"; 26 | init(); 27 | ``` 28 | 29 | ## Install 30 | 31 | **NPM and Meteor 1.3** 32 | 33 | npm install angular2-now 34 | 35 | **BOWER** 36 | 37 | bower install angular2-now 38 | 39 | **Meteor** 40 | 41 | meteor add pbastowski:angular2-now 42 | 43 | **CDN** 44 | 45 | ```html 46 | 47 | 48 | 49 | 50 | 51 | ``` 52 | 53 | ## Usage with ES6 54 | 55 | Use angular2-now with an **ES6 transpiler** like **Babel** or **TypeScript**. Both work equally well. 56 | 57 | Include angular2-now in your AngularJS project, ensuring that it loads before any of it's functions are used. If you're not using any module loaders, then `window.angular2now` gives you direct access to all the annotation functions. 58 | 59 | > See the **Examples and Demos** section below for examples. 60 | 61 | ### With SystemJS 62 | 63 | If your app loads SystemJS before angular2-now, then angular2-now will register itself with SystemJS and you will be able to import annotations as shown below. 64 | 65 | ```javascript 66 | import {Component, View, Inject, bootstrap, Options} from 'angular2now'; 67 | ``` 68 | 69 | ### With Meteor 70 | 71 | With Meteor 1.2 you will be using `angular2-now` in combination with `angular-meteor`, whose package name is simply `angular`. `angular-meteor` automatically includes `pbastowski:angular-babel`, which provides ES6 (ES2015) support. So, there is no need for you to add Babel to your Meteor project explicitly. You can also use TypeScript, if you want, by adding the package `pbastowski:typescript` to your project. 72 | 73 | #### Meteor and SystemJS module loader 74 | 75 | SystemJS support is provided by adding the package `pbastowski:systemjs` to your project. Make sure to read the [README](https://github.com/pbastowski/angular-meteor-babel/tree/meteor1.2) for `pbastowski:angular-babel` to understand: 76 | - how to enable SystemJS support and 77 | - how `angular-babel` names SystemJS modules in your project 78 | 79 | Otherwise, you might have trouble importing from them. 80 | 81 | #### Meteor without SystemJS (the old way) 82 | 83 | Meteor does not need any kind of module loader, because it bundles and loads your files according to its [convention](http://docs.meteor.com/#/full/fileloadorder). This may be enough for you, if you're happy to use angular2-now through the globally visible `window.angular2now` object. 84 | 85 | On the other hand, if you like to use ES6 `import ... from` statements in your project and don't want to use SystemJS, then add the package `pbastowski:require` to your project. It provides basic `module.exports` functionality in the browser and will allow you to export like this 86 | 87 | **MyService.js** 88 | 89 | ```javascript 90 | export class MyService { } 91 | 92 | export var things = { 93 | thing1, 94 | thing2 95 | } 96 | ``` 97 | 98 | And import like this 99 | 100 | **MyComponent.js** 101 | 102 | ```javascript 103 | import "MyService"; 104 | 105 | import {thing1} from "things" 106 | ``` 107 | 108 | > When using `pbastowski:require` individual objects are exported by their name. There is no concept of a module, as such. Think of exporting as making the object global. In fact you can also access the exported object through `window.things` or `window.MyService`. 109 | 110 | In the above example, when we `import "MyService"` we are actually importing the whole class object, whereas `thing1` is the only object imported from `things`. 111 | 112 | ## Which Angular 2 annotations can I use in my Angular 1 apps? 113 | 114 | The following annotations have been implemented to support Angular 2.0 component syntax. Any parameters preceeded with `?` are optional. 115 | 116 | ```javascript 117 | // SetModule is not actually in Angular2, but is required in Angular1 118 | // in place of angular.module(). 119 | SetModule('my-app', ['angular-meteor']); 120 | 121 | @Component({ 122 | selector: 'my-app', 123 | ?template: '
Inline template
', // inline template 124 | ?templateUrl: 'path/to/the_template.html', // importing a template 125 | ?bind: { twoWay: '=', value: '@', function: '&' }, 126 | ?providers: ['$http', '$q', 'myService'], // alias for @Inject 127 | ?replace: true or false, 128 | ?transclude: true or false, 129 | ?scope: undefined or true or same as bind 130 | }) 131 | 132 | // View is optional, as all it's properties are also available in @Component 133 | @View({ 134 | template: '
Inline template
', // inline template 135 | templateUrl: 'path/to/the_template.html', // importing a template 136 | ?transclude: true or false 137 | }) 138 | 139 | // Inject is optional, as injected objects can be specified in the 140 | // providers property of @Component 141 | @Inject('$http', '$q'); // Passing injectables directly 142 | // Also valid: @Inject(['$http', '$q']) 143 | 144 | class App { 145 | constructor($http, $q) { } 146 | } 147 | 148 | bootstrap(App, ?config); // config is optional 149 | ``` 150 | 151 | The annotations below are not Angular 2, but for me they make coding in Angular a bit nicer. 152 | 153 | ```javascript 154 | @Service({ name: 'serviceName' }) 155 | 156 | @Filter({ name: 'filterName' }) 157 | 158 | @Directive() // alias for @Component 159 | 160 | @ScopeShared() // same as { scope: undefined } on @Directive 161 | 162 | @ScopeNew() // same as { scope: true } on @Directive 163 | ``` 164 | 165 | Client-side routing with ui-router 166 | ```javascript 167 | @State({ 168 | name: 'stateName', 169 | ?url: '/stateurl', 170 | ?defaultRoute: true/false or '/default/route/url', 171 | ?abstract: true or false, 172 | ?html5Mode: true/false, 173 | ?params: { id: 123 }, // default params, see ui-router docs 174 | ?data: { a: 1, b: 2}, // custom data 175 | ?resolve: {...}, 176 | ?controller: controllerFunction, 177 | ?template: '
', 178 | ?templateUrl: 'client/app/app.html', 179 | ?templateProvider: function() { return "

content

"; } 180 | })) 181 | ``` 182 | 183 | ### Meteor specific annotations 184 | 185 | The annotation below will only work with Meteor. 186 | 187 | ```javascript 188 | @MeteorMethod( ?options ) 189 | ``` 190 | 191 | ## Examples and Demos 192 | 193 | Please visit the following github repositories and Plunker examples before you start coding. It will save you some "WTF" time. 194 | 195 | #### ES6 example 196 | 197 | [ES6 Angular2-now Plunker](http://plnkr.co/edit/JhHlOr?p=preview) 198 | 199 | #### Meteor examples on GitHub 200 | 201 | [Thinkster-MEAN-Tutorial-in-angular-meteor](https://github.com/pbastowski/Thinkster-MEAN-Tutorial-in-angular-meteor/tree/feature/ng2-now-with-services) 202 | 203 | [meteor-angular-socially](https://github.com/pbastowski/meteor-angular-socially/tree/feature/ng2now) 204 | 205 | [todo-ng2now](https://github.com/pbastowski/todo-ng2now) 206 | 207 | 208 | ## API in-depth 209 | 210 | ### Component vs Directive 211 | 212 | `Directive` is an alias for `Component`, which means it does the same thing, but is spelled different. The main difference between directives and components is that directives have no template HTML. A Directive is an attribute on an existing HTML element that simply adds new behaviour to that element. It is one attribute amongst any number of other attributes on an element. 213 | 214 | There is an implication to this, in that AngularJS only allows one directive to have isolate scope on the same HTML element. By default, `Component` creates isolate scope and since `Directive` is an alias for `Component` it also creates isolate scope. This sometimes causes issues. 215 | 216 | To overcome that, you can use a couple of annotations: 217 | - `ScopeShared` same as passing `{ scope: undefined }` to `@Directive` 218 | - `ScopeNew` same as passing `{ scope: true }` to `@Directive` 219 | 220 | ```javascript 221 | @Directive({ ... }) 222 | @ScopeShared() 223 | class MyDirective { } 224 | ``` 225 | 226 | ### SetModule instead of angular.module 227 | 228 | ```javascript 229 | SetModule( 'app', ['angular-meteor', 'ui.router', 'my-other-module'] ) 230 | ``` 231 | 232 | You must use `SetModule` at least once in your app, before you use any annotations, to tell angular2-now in which module to create all Components, Services, Filters and State configuration. The syntax is identical to Angular's own [angular.module()](https://docs.angularjs.org/api/ng/function/angular.module). Use `SetModule` in the same places you would normally use `angular.module`. 233 | 234 | ### ui-router support through @State 235 | 236 | This is completely not Angular 2, but I love how easy it makes my routing. You'll have to include ui-router in your app 237 | 238 | Meteor: 239 | 240 | meteor add angularui:angular-ui-router 241 | 242 | Bower: 243 | 244 | bower install angular-ui-router 245 | 246 | And then add the `ui.router` dependency to your bootstrap module, like this 247 | 248 | SetModule('myApp', ['angular-meteor', 'ui.router']); 249 | 250 | Then, you can simply annotate your component with the route/state info, like so 251 | 252 | ```javascript 253 | @State({name: 'defect', url: '/defect', defaultRoute: true}) 254 | 255 | @Component({selector: 'defect'}) 256 | @View({templateUrl: 'client/defect/defect.html'}) 257 | @Inject(['lookupTables']) 258 | class Defect { 259 | } 260 | ``` 261 | 262 | #### defaultRoute 263 | 264 | ```javascript 265 | { name: 'root', url: '' } 266 | { name: 'root.defect', url: '/defect', defaultRoute: '/defect' } 267 | { name: 'root.defect.report', url: '/report', defaultRoute: '/defect/report' } 268 | { name: 'root.defect', url: '/defect', defaultRoute: true } 269 | ``` 270 | 271 | The `defaultRoute` property makes the annotated state the default for your app. That is, if the user types an unrecognised path into the address bar, or does not type any path other than the url of your app, they will be redirected to the path specified in defaultRoute. It is a bit like the old 404 not found redirect, except that in single page apps there is no 404. There is just the default page (or route). 272 | 273 | > Meteor's web server automatically redirects all unrecognised routes to the app root "/". However, if you're not using Meteor, you'll want to make sure that all unrecognised routes are redirected to the app root, which in many cases is "/". 274 | 275 | Note that `defaultRoute: true` only works when the state's `url` is the same as it's defaultRoute. 276 | 277 | For example 278 | 279 | ```javascript 280 | { name: 'root.defect', url: '/defect', defaultRoute: '/defect' } 281 | ``` 282 | 283 | can be replaced with 284 | 285 | ```javascript 286 | { name: 'root.defect', url: '/defect', defaultRoute: true } 287 | ``` 288 | 289 | For nested states, where the default state has parent states with their own URLs, always specify the `defaultRoute` as a string that represents the final URL that you want the app to navigate to by default. 290 | 291 | #### Resolving Values 292 | 293 | A `ui-router` resolve block can be added to the @State annotation, as shown below. 294 | 295 | ```javascript 296 | @State({ 297 | name: 'defect', 298 | url: '/defect', 299 | defaultRoute: true, 300 | resolve: { 301 | user: ['$q', function($q) { return 'paul'; }], 302 | role: function() { return 'admin'; } 303 | } 304 | }) 305 | 306 | @Component({ selector: 'defect' }) 307 | @View({ tamplateUrl: 'client/defect/defect.html' }) 308 | @Inject('defect') 309 | 310 | class Defect { 311 | constructor(defect) { 312 | // defect.name == 'paul' 313 | // defect.role == 'admin' 314 | } 315 | } 316 | ``` 317 | 318 | Adding a @State annotation to a Component does NOT make the component's Class the State's controller and thus you can't directly inject resolved values into it. This is, because the Component's Class is the Component's controller and can not also be reused as the State's controller. 319 | 320 | Read on for how to inject the resolved values into your component's controller. 321 | 322 | #### Injecting Resolved Dependencies into your component's controller 323 | 324 | The resolved values are made available for injection into a component's constructor, as shown in the example above. The injected parameter `defect` is the name of a service automatically created for you, which holds the resolved return values. The name of this service is always the camelCased version of your component's selector. So, if the selector == 'my-app', then the name of the injectable service will be 'myApp'. 325 | 326 | 327 | #### States without a component 328 | 329 | It is also possible to define a state without a component, as shown below, provided that you do not also annotate it's Class as a Component. 330 | 331 | ```javascript 332 | @State({ 333 | name: 'test', 334 | url: '/test', 335 | resolve: { 336 | user: function() { return 'paul'; }, 337 | role: function() { return 'admin'; } 338 | } 339 | }) 340 | class App { 341 | constructor(user, role) { 342 | console.log('myApp resolved: ', user, role); 343 | } 344 | } 345 | ``` 346 | 347 | In this case, the class constructor is the controller for the route and receives the injected properties directly (as per ui-router documentation). 348 | 349 | ### Bootstrapping the app 350 | 351 | This allows you to bootstrap your Angular 1 app using the Angular 2 component bootstrap syntax. There is no need to use `ng-app`. 352 | 353 | ```javascript 354 | bootstrap (App [, config ]) 355 | ``` 356 | 357 | Using `bootstrap` is the equivalent of the Angular 1 manual bootstrapping method: `angular.bootstrap(DOMelement, ['app'])`. The bootstrap function also knows how to handle Cordova apps. 358 | `config` is the same parameter as in [angular.bootstrap()](https://code.angularjs.org/1.3.15/docs/api/ng/function/angular.bootstrap). It can be used to enforce strictDi, for testing before deployment to production. 359 | 360 | #### An example showing how to bootstrap an app 361 | 362 | In your HTML body add this: 363 | 364 | ```html 365 | Optional content inside my app that can be transcluded 366 | ``` 367 | 368 | And in your JavaScript add the code below. 369 | 370 | ```javascript 371 | SetModule('my-app', []); 372 | 373 | @Component({selector: 'my-app' }) 374 | @View({template: ``}) 375 | class App { 376 | } 377 | 378 | bootstrap(App); 379 | ``` 380 | 381 | > The bootstrap module must have the same name as the bootstrap component's selector. 382 | 383 | ### ControllerAs syntax 384 | 385 | The created components use `ControllerAs` syntax. So, when referring to properties or functions on the controller's "scope", make sure to prefix them with `this` in the controller and with the camel-cased selector name in the HTML templates. If the component's selector is `home-page` then your html might look like this: 386 | 387 | ```html 388 |
389 | ``` 390 | 391 | #### Can I use `vm` instead of `homePage`? 392 | 393 | Sure. If you want to use `vm` as the controller name for a specific component, then do this: 394 | 395 | ```javascript 396 | @Component({ selector: 'defect', controllerAs: 'vm' }) 397 | class Defect { 398 | test() {} 399 | } 400 | ``` 401 | 402 | and then in your HTML template you will then be able do this: 403 | 404 | ```html 405 |
406 | ``` 407 | 408 | #### I want to use "vm" as the name of all my component's controllers 409 | 410 | No problem. Just configure angular2-now to use `vm` instead, like this 411 | 412 | ```javascript 413 | import {options} from 'angular2now'; 414 | options({ controllerAs: 'vm' }) 415 | ``` 416 | 417 | Do this before you use any angular2-now components! 418 | 419 | 420 | ### Transclusion 421 | 422 | #### Inline templates 423 | 424 | If your inline `template` includes `` then `@View` will automatically add `ng-transclude` to it and internally the directive's `transclude` flag will be set to `true`. 425 | 426 | So, this inline HTML template 427 | 428 | ```html 429 | h2 This is my header 430 | 431 | ``` 432 | 433 | will be automatically changed to look like this 434 | 435 | ```html 436 | h2 This is my header 437 | 438 | ``` 439 | 440 | #### `templateUrl` and transclusion 441 | 442 | Templates specified using the `templateUrl` property aren't currently checked and thus do not get `ng-transclude` added to them by `@View`. You will have to manually add ng-transclude to the element you want to transclude in your template. You will also need to add `transclude: true` to the @View annotation's options, as shown below: 443 | 444 | ```javascript 445 | @View({ templateUrl: '/client/mytemplate.html', transclude: true }) 446 | ``` 447 | 448 | ### How do I access `ngModel` and other component's controllers? 449 | 450 | You `@Inject` the names of the components whose controllers you want. Prefix each controller name with `"@"` or `"@^"` (looks for a parent controller). These dependencies are not directly injected into the constructor (controller), because they are not available at the time the constructor executes, but at `link` time (see AngularJS documentation about this). However, they can be accessed within the constructor like this: 451 | 452 | ```javascript 453 | @Component({ selector: 'tab' }) 454 | @Inject('@ngModel', '@^tabContainer') 455 | class Tab { 456 | constructor() { 457 | 458 | this.$dependson = function (ngModel, tabContainer) { 459 | ngModel.$parsers.unshift(function (value) { ... }); 460 | 461 | // This gives you access to tabContainer's scope methods and properties 462 | tabContainer.someFunction(); 463 | if (tabContainer.tabCount === 0) { ... } 464 | } 465 | } 466 | } 467 | ``` 468 | 469 | Please note that the injected component controllers are not listed as arguments to the constructor. 470 | 471 | ### angular2-now `options` 472 | 473 | Below is the list of angular2-now options that can be changed. 474 | 475 | Attribute | Type | Description 476 | ----------|------|------------------- 477 | controllerAs | string | Allows you to specify a default controllerAs prefix to use for all components. The default prefix is the camel-cased version of the component's selector. 478 | spinner | object | Exposes show() and hide() methods, that show and hide a busy-spinner 479 | events | object | Exposes beforeCall() and afterCall(), which will be called before and after the ajax call. Only `afterCall` is guaranteed to run after the call to the MeteorMethod completes. 480 | 481 | 482 | Options can be defined or changed like this: 483 | 484 | ```javascript 485 | import {options} from 'angular2now'; 486 | 487 | options({ 488 | spinner: { 489 | show: function () { document.body.style.background = 'yellow'; }, 490 | hide: function () { document.body.style.background = ''; } 491 | }, 492 | events: { 493 | beforeCall: () => console.log('< BEFORE call'), 494 | afterCall: () => console.log('> AFTER call'), 495 | } 496 | }) 497 | ``` 498 | 499 | Do this before executing any other angular2-now code. 500 | 501 | 502 | ## Meteor Helper Annotations 503 | 504 | ### MeteorMethod 505 | 506 | ```javascript 507 | @MeteorMethod( ?options ) 508 | ``` 509 | 510 | The `MeteorMethod` annotation is used to create a client-side method that calls a procedure defined on the Meteor server. The `options` argument is optional. Here is an example. 511 | 512 | On the server side you create the Meteor method like this: 513 | 514 | ```javascript 515 | Meteor.methods({ 516 | sendEmail: function(from, to, subject, body) { 517 | try { 518 | Email.send({from: from, to: to, subject: subject, text: body}); 519 | } catch (er) { 520 | return er; 521 | } 522 | } 523 | }) 524 | ``` 525 | 526 | On the client side, you annotate a stub method, in this case `sendEmail(){}`, in your `Service` or `Component` class with `@MeteorMethod()`. The name of the stub method must be the same as the name of the Meteor method on the server: 527 | 528 | ```javascript 529 | class Mail { 530 | @MeteorMethod() 531 | sendEmail() { } 532 | } 533 | ``` 534 | 535 | And then you call ` sendEmail() ` somewhere, like this: 536 | 537 | ```javascript 538 | @Inject('mail') 539 | class MyComponent { 540 | constructor(mail) { 541 | mail.sendEmail('me@home.com', 'you@wherever.net', 'hello', 'Hi there!') 542 | .then( () => console.log('success'), (er) => console.log('Error: ', er) ); 543 | } 544 | } 545 | ``` 546 | 547 | #### The `options` argument 548 | 549 | The `options` argument of `MeteorMethod` allows you to override global options on a per-method basis. To find out what global angular2-now options are available please the **angular2-now Options** section. 550 | 551 | When defining a `MeteorMethod`, the options can be overridden like this: 552 | 553 | ```javascript 554 | @MeteorMethod({ spinner: { ... }, events: { ... }) 555 | sendEmail() {} 556 | ``` 557 | 558 | ## Breaking changes between 1.0.0 and 0.3.15 559 | 560 | - `angular.module` is no longer monkey-patched by angular2-now. You must use `SetModule` instead of `angular.module` for all modules where you wish to use angular2-now. SetModule has the exact same syntax as angular.module. This change was necessary due to problems encountered with the monkey-patching approach under certain conditions. 561 | 562 | ## What environment is required? 563 | - Angular 1.4+ 564 | - Babel 5.1.10+ 565 | - Meteor 1.2+ 566 | 567 | ### Browsers 568 | - IE9+ 569 | - Chrome 570 | - FireFox 571 | - Safari desktop and mobile (IOS 7 or better) 572 | 573 | ## I need more Angular 2 features 574 | 575 | Then you may want to have a look at [ng-forward](https://github.com/ngUpgraders/ng-forward). It is a very comprehensive library with a lot of Angular 2 features and it is developed by really clever people, whom I had the pleasure to work with :) 576 | 577 | ### But I really love angular2-now, so, can't you just add more Angular 2 features? 578 | 579 | The short answer is no, because I designed `angular2-now` for a specific purpose with narrow requirements: 580 | 581 | - make Angular 1 coding simple and fun for myself and my team 582 | - make me think of web apps in terms of components within components, instead of HTML + controllers + directives + ui-router 583 | - make ui-router configuration simple (because it is not) 584 | 585 | As it stands now, the above three requirements are satisfied for myself, but if you would like to contribute then I am happy to consider a PR. 586 | 587 | ## Contributing 588 | 589 | If you think you have a great feature that should be incorporated in the main library, or a fix for a bug, or some doco updates then please send me a PR. 590 | 591 | When sending code changes or new code make sure to describe in details what it is that you are trying to achieve and what the code does. I am not going to accept pure code without detailed descriptions of what it does and why. 592 | 593 | ### Contributors 594 | 595 | Over time there were varied contributors to this repo, however, below are those that had the biggest influence: 596 | 597 | - Paul Bastowski (pbastowski) 598 | - Uri Goldshtein (urigo) 599 | - Kamil Kisiela (kamilkisiela) 600 | - Aaron Roberson (aaronroberson) 601 | - Dotan Simha (dotansimha) 602 | -------------------------------------------------------------------------------- /dist/angular2-now.js: -------------------------------------------------------------------------------- 1 | /*! angular2-now v1.1.6 */ 2 | (function webpackUniversalModuleDefinition(root, factory) { 3 | if(typeof exports === 'object' && typeof module === 'object') 4 | module.exports = factory(); 5 | else if(typeof define === 'function' && define.amd) 6 | define([], factory); 7 | else if(typeof exports === 'object') 8 | exports["angular2now"] = factory(); 9 | else 10 | root["angular2now"] = factory(); 11 | })(this, function() { 12 | return /******/ (function(modules) { // webpackBootstrap 13 | /******/ // The module cache 14 | /******/ var installedModules = {}; 15 | 16 | /******/ // The require function 17 | /******/ function __webpack_require__(moduleId) { 18 | 19 | /******/ // Check if module is in cache 20 | /******/ if(installedModules[moduleId]) 21 | /******/ return installedModules[moduleId].exports; 22 | 23 | /******/ // Create a new module (and put it into the cache) 24 | /******/ var module = installedModules[moduleId] = { 25 | /******/ exports: {}, 26 | /******/ id: moduleId, 27 | /******/ loaded: false 28 | /******/ }; 29 | 30 | /******/ // Execute the module function 31 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 32 | 33 | /******/ // Flag the module as loaded 34 | /******/ module.loaded = true; 35 | 36 | /******/ // Return the exports of the module 37 | /******/ return module.exports; 38 | /******/ } 39 | 40 | 41 | /******/ // expose the modules object (__webpack_modules__) 42 | /******/ __webpack_require__.m = modules; 43 | 44 | /******/ // expose the module cache 45 | /******/ __webpack_require__.c = installedModules; 46 | 47 | /******/ // __webpack_public_path__ 48 | /******/ __webpack_require__.p = ""; 49 | 50 | /******/ // Load entry module and return exports 51 | /******/ return __webpack_require__(0); 52 | /******/ }) 53 | /************************************************************************/ 54 | /******/ ([ 55 | /* 0 */ 56 | /***/ function(module, exports, __webpack_require__) { 57 | 58 | 'use strict'; 59 | 60 | Object.defineProperty(exports, '__esModule', { 61 | value: true 62 | }); 63 | 64 | var _common = __webpack_require__(1); 65 | 66 | var _apiSetModule = __webpack_require__(2); 67 | 68 | var _apiComponent = __webpack_require__(3); 69 | 70 | var _apiScopeShared = __webpack_require__(7); 71 | 72 | var _apiScopeNew = __webpack_require__(8); 73 | 74 | var _apiView = __webpack_require__(4); 75 | 76 | var _apiInject = __webpack_require__(5); 77 | 78 | var _apiController = __webpack_require__(9); 79 | 80 | var _apiService = __webpack_require__(10); 81 | 82 | var _apiFilter = __webpack_require__(11); 83 | 84 | var _apiBootstrap = __webpack_require__(12); 85 | 86 | var _apiState = __webpack_require__(13); 87 | 88 | var _apiOptions = __webpack_require__(14); 89 | 90 | var _apiMeteorMethod = __webpack_require__(15); 91 | 92 | var _apiMeteorReactive = __webpack_require__(16); 93 | 94 | var _apiLocalInjectables = __webpack_require__(17); 95 | 96 | var angular2now = { 97 | init: init, 98 | 99 | SetModule: _apiSetModule.SetModule, 100 | 101 | Component: _apiComponent.Component, 102 | ScopeShared: _apiScopeShared.ScopeShared, 103 | ScopeNew: _apiScopeNew.ScopeNew, 104 | View: _apiView.View, 105 | Inject: _apiInject.Inject, 106 | Controller: _apiController.Controller, 107 | Service: _apiService.Service, 108 | Filter: _apiFilter.Filter, 109 | bootstrap: _apiBootstrap.bootstrap, 110 | State: _apiState.State, 111 | 112 | options: _apiOptions.options, 113 | Options: _apiOptions.Options, 114 | 115 | MeteorMethod: _apiMeteorMethod.MeteorMethod, 116 | MeteorReactive: _apiMeteorReactive.MeteorReactive, 117 | LocalInjectables: _apiLocalInjectables.LocalInjectables, 118 | 119 | Directive: _apiComponent.Component, 120 | Injectable: _apiService.Service 121 | }; 122 | 123 | function init() { 124 | _common.common.isCordova = typeof cordova !== 'undefined'; 125 | _common.common.angularModule = angular.module; 126 | } 127 | 128 | if (typeof Meteor === 'undefined') { 129 | init(); 130 | } 131 | 132 | if (typeof window !== 'undefined') { 133 | window.angular2now = angular2now; 134 | } 135 | 136 | exports['default'] = angular2now; 137 | module.exports = exports['default']; 138 | 139 | /***/ }, 140 | /* 1 */ 141 | /***/ function(module, exports) { 142 | 143 | 'use strict'; 144 | 145 | Object.defineProperty(exports, '__esModule', { 146 | value: true 147 | }); 148 | var common = { 149 | angularModule: undefined, 150 | currentModule: undefined, 151 | currentNameSpace: undefined, 152 | isCordova: false, 153 | ng2nOptions: { 154 | currentModule: function currentModule() { 155 | return common.currentModule; 156 | } 157 | }, 158 | controllerAs: undefined, 159 | $q: angular.injector(['ng']).get('$q') 160 | }; 161 | exports.common = common; 162 | 163 | /***/ }, 164 | /* 2 */ 165 | /***/ function(module, exports, __webpack_require__) { 166 | 167 | 'use strict'; 168 | 169 | Object.defineProperty(exports, '__esModule', { 170 | value: true 171 | }); 172 | exports.SetModule = SetModule; 173 | 174 | var _common = __webpack_require__(1); 175 | 176 | function SetModule() { 177 | /** 178 | * Name-spacing applies to provider names, not modules. Each module 179 | * has to have a unique name of it's own. 180 | * 181 | * A namespace may be specified like this: 182 | * SetModule('ftdesiree:helpers') 183 | * The namespace, once set, will remain in force until removed. 184 | * Remove the namespace like this: 185 | * angular.module(':helpers') 186 | **/ 187 | _common.common.currentModule = arguments[0].split(':'); 188 | 189 | if (_common.common.currentModule.length === 1) { 190 | // No namespace, just the module name 191 | _common.common.currentModule = _common.common.currentModule[0]; 192 | } else { 193 | // Split off the name-space and module name 194 | _common.common.currentNameSpace = _common.common.currentModule[0]; 195 | _common.common.currentModule = _common.common.currentModule[1]; 196 | 197 | // Reassign arguments[0] without the namespace 198 | arguments[0] = _common.common.currentModule; 199 | } 200 | 201 | return _common.common.angularModule.apply(angular, arguments); 202 | } 203 | 204 | /***/ }, 205 | /* 3 */ 206 | /***/ function(module, exports, __webpack_require__) { 207 | 208 | 'use strict'; 209 | 210 | Object.defineProperty(exports, '__esModule', { 211 | value: true 212 | }); 213 | exports.Component = Component; 214 | 215 | var _view = __webpack_require__(4); 216 | 217 | var _inject = __webpack_require__(5); 218 | 219 | var _common = __webpack_require__(1); 220 | 221 | var _utils = __webpack_require__(6); 222 | 223 | // function Directive(options) { 224 | // 225 | // // A string passed is assumed to be the attribute name of the directive. 226 | // if (typeof options === 'string') 227 | // options = { selector: options }; 228 | // 229 | // // Directives have shared scope by default (scope:undefined). 230 | // // Optionally they can have a new scope created (scope: true). 231 | // // If you require an isolate scope for your directive then 232 | // // pass "scope: { ... }" in options. 233 | // if (options && !options.hasOwnProperty('scope')) 234 | // angular.merge(options, { scope: undefined }); 235 | // 236 | // return Component(options); 237 | // } 238 | 239 | function Component(options) { 240 | options = options || {}; 241 | // Allow shorthand notation of just passing the selector name as a string 242 | if (typeof options === 'string') { 243 | options = { 244 | selector: options 245 | }; 246 | } 247 | 248 | return function ComponentTarget(target) { 249 | var isClass = false; 250 | 251 | // Create a stub controller and substitute it for the target's constructor, 252 | // so that we can call the target's constructor later, within the link function. 253 | target = deferController(target, controller); 254 | 255 | // service injections, which could also have been specified by using @Inject 256 | if (options.injectables && options.injectables instanceof Array) { 257 | target = (0, _inject.Inject)(options.injectables)(target); 258 | } 259 | // injectables has been renamed to services 260 | if (options.services && options.services instanceof Array) { 261 | target = (0, _inject.Inject)(options.services)(target); 262 | } 263 | // injectables has been renamed to providers, actually, but also keeping 264 | // services in case anyone has used it already. 265 | if (options.providers && options.providers instanceof Array) { 266 | target = (0, _inject.Inject)(options.providers)(target); 267 | } 268 | 269 | // Selector name may be prefixed with a '.', in which case "restrict: 'C'" will be used 270 | options.selector = (0, _utils.camelCase)(options.selector || '') + ''; 271 | if (options.selector[0] === '.') { 272 | isClass = true; 273 | options.selector = options.selector.slice(1); 274 | } 275 | // Save the unCamelCased selector name, so that bootstrap() can use it 276 | target.selector = (0, _utils.unCamelCase)(options.selector); 277 | 278 | // template options can be set with Component or with View 279 | // so, we run View on the passed in options first. 280 | if (options.template || options.templateUrl || options.transclude || options.directives) { 281 | (0, _view.View)(options)(target); 282 | } 283 | 284 | // The template(Url) can also be passed in from the @View decorator 285 | options.template = target.template || undefined; 286 | options.templateUrl = target.templateUrl || undefined; 287 | 288 | // Build the require array. 289 | // Our controller needs the same injections as the component's controller, 290 | // but with the "@*" injections renamed to "$scope". The link function will pass 291 | // the "@*" injections directly to the component controller. 292 | var requiredControllers = [options.selector]; 293 | 294 | target.$inject = target.$inject || []; 295 | target.$inject = target.$inject.map(function (dep) { 296 | if (/^@[^]{0,2}/.test(dep[0])) { 297 | requiredControllers.push('?' + dep.slice(1)); 298 | dep = 'delete-me'; 299 | } 300 | return dep; 301 | }); 302 | 303 | // Remove all the 'delete-me' entries 304 | target.$inject = target.$inject.filter(function (v) { 305 | return v !== 'delete-me'; 306 | }); 307 | 308 | if (target.meteorReactive) { 309 | // Prepend angular-meteor injectables 310 | target.$inject.unshift('$scope'); 311 | target.$inject.unshift('$reactive'); 312 | } 313 | 314 | // Remember the original $inject, as it will be needed in the link function. 315 | // In the link function we will receive any requested component controllers 316 | // which we will then inject into the arguments that we will pass to the 317 | // actual constructor of our component. 318 | target.$injectDefer = target.$inject || []; 319 | 320 | // Create the angular directive 321 | var ddo = { 322 | controllerAs: options.controllerAs || _common.common.controllerAs || target.controllerAs || options.selector, 323 | bindToController: typeof target.bindToController === 'boolean' ? target.bindToController : true, 324 | restrict: options.template + options.templateUrl ? 'EA' : isClass ? 'C' : 'A', 325 | scope: {}, 326 | template: options.template, 327 | templateUrl: options.templateUrl, 328 | controller: target, 329 | replace: options.replace || false, 330 | transclude: /ng-transclude/i.test(options.template) || target.transclude, 331 | require: options.require || target.require || requiredControllers, 332 | link: options.link || target.link || link 333 | }; 334 | 335 | // ddo's restrict 336 | if (options.restrict) { 337 | ddo.restrict = options.restrict; 338 | } 339 | // ddo's scope 340 | if (target.hasOwnProperty('scope')) { 341 | ddo.scope = target.scope; 342 | } else if (options.hasOwnProperty('scope')) { 343 | ddo.scope = options.scope; 344 | } else if (options['bind']) { 345 | ddo.scope = options['bind']; 346 | } 347 | 348 | try { 349 | angular.module(_common.common.currentModule).directive(options.selector, function () { 350 | return ddo; 351 | }); 352 | } catch (er) { 353 | throw new Error('Does module "' + _common.common.currentModule + '" exist? You may need to use SetModule("youModuleName").'); 354 | } 355 | 356 | return target; 357 | 358 | // The stub controller below saves injected objects, so we can re-inject them 359 | // into the "real" controller when the link function executes. 360 | // This allows me to add stuff to the controller and it's "this", which is required 361 | // for some future functionality. 362 | function controller() { 363 | var ctrlInstance = this; 364 | var toInjectAfter = []; 365 | 366 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 367 | args[_key] = arguments[_key]; 368 | } 369 | 370 | var injectedDeps = args; 371 | 372 | if (target.meteorReactive) { 373 | // Get injected angular-meteor objects 374 | var $reactive = injectedDeps[0]; 375 | var $scope = injectedDeps[1]; 376 | 377 | $reactive(ctrlInstance).attach($scope); 378 | 379 | toInjectAfter = injectedDeps.slice(0, 2); 380 | injectedDeps = injectedDeps.slice(2); 381 | target.$inject = target.$inject.slice(2); 382 | } 383 | if (target.localInjectables) { 384 | target.$inject.forEach(function (value, index) { 385 | ctrlInstance[value] = injectedDeps[index]; 386 | }); 387 | } 388 | // Call the original constructor, which is now called $$init, injecting all the 389 | // dependencies requested. 390 | this.$$init.apply(this, injectedDeps); 391 | 392 | if (toInjectAfter.length > 0) { 393 | target.$inject = ['$reactive', '$scope'].concat(target.$inject); 394 | injectedDeps.unshift(toInjectAfter[1]); 395 | injectedDeps.unshift(toInjectAfter[0]); 396 | } 397 | } 398 | // This function allows me to replace a component's "real" constructor with my own. 399 | // I do this, because I want to decorate the $scope and this before instantiating 400 | // the class's original controller. Also, this enables me to inject 401 | // other component's controllers into the constructor, the same way as you would 402 | // inject a service. 403 | // The component's original constructor is assigned to the init method of the 404 | // component's class, so that when it executes it will run in the original scope and 405 | // closures that it was defined in. It is the init method that is called within the 406 | // link function. 407 | function deferController(target, controller) { 408 | // Save the original prototype 409 | var oldproto = target.prototype; 410 | // Save the original constructor, so we can call it later 411 | var construct = target.prototype.constructor; 412 | // Save any static properties 413 | var staticProps = {}; 414 | 415 | for (var i in target) { 416 | staticProps[i] = target[i]; 417 | } 418 | // Assign a new constructor, which holds the injected deps. 419 | target = controller; 420 | // Restore the original prototype 421 | target.prototype = oldproto; 422 | // Restore saved static properties 423 | for (var i in staticProps) { 424 | target[i] = staticProps[i]; 425 | } 426 | // Store the original constructor under the name $$init, 427 | // which we will call in the link function. 428 | target.prototype.$$init = construct; 429 | // Hide $$init from the user's casual inspections of the controller 430 | // Object.defineProperty(target.prototype, "$$init", {enumerable: false}) 431 | return target; 432 | } 433 | 434 | function link(scope, el, attr, controllers) { 435 | // Create a service with the same name as the selector 436 | // That holds a reference to our component 437 | // angular.module(currentModule).value(camelCase(target.selector), controllers[0]); 438 | 439 | // Alternate syntax for the injection of other component's controllers 440 | if (controllers[0].$dependson) { 441 | controllers[0].$dependson.apply(controllers[0], controllers.slice(1)); 442 | } 443 | } 444 | }; 445 | } 446 | 447 | /***/ }, 448 | /* 4 */ 449 | /***/ function(module, exports) { 450 | 451 | 'use strict'; 452 | 453 | Object.defineProperty(exports, '__esModule', { 454 | value: true 455 | }); 456 | exports.View = View; 457 | 458 | function View(options) { 459 | options = options || {}; 460 | // Allow shorthand notation of just passing the templateUrl as a string 461 | if (typeof options === 'string') { 462 | options = { 463 | templateUrl: options 464 | }; 465 | } 466 | 467 | // if (!options.template) options.template = undefined; 468 | 469 | return function ViewTarget(target) { 470 | target.template = options.template || target.template; 471 | target.templateUrl = options.templateUrl || target.templateUrl; 472 | 473 | // When a templateUrl is specified in options, then transclude can also be specified 474 | target.transclude = options.transclude || target.transclude; 475 | 476 | // directives is an array of child directive controllers (Classes) 477 | target.directives = options.directives || target.directives; 478 | 479 | // Check for the new tag and add ng-transclude to it, if not there. 480 | if (target.template) { 481 | target.template = transcludeContent(target.template); 482 | } 483 | 484 | return target; 485 | }; 486 | 487 | // If template contains the new tag then add ng-transclude to it. 488 | // This will be picked up in @Component, where ddo.transclude will be set to true. 489 | function transcludeContent(template) { 490 | var s = (template || '').match(/\]([^\>]+)/i); 491 | 492 | if (s && s[1].toLowerCase().indexOf('ng-transclude') === -1) { 493 | template = template.replace(/\". 973 | // 4) Otherwise, we provide the following default template "
". 974 | // (**) The bootstrap component will be rendered by Angular directly and must not 975 | // be rendered again by ui-router, or you will literally see it twice. 976 | // todo: allow the user to specify their own div/span instead of forcing "div(ui-view)" 977 | template: (target.template || target.templateUrl) && !target.bootstrap && target.selector ? target.selector.replace(/^(.*)$/, '<$1>') : '
', 978 | 979 | // The option for dynamically setting a template based on local values 980 | // or injectable services 981 | templateProvider: options.templateProvider, 982 | 983 | // Do we need to resolve stuff? If so, then we also provide a controller to catch the resolved data. 984 | resolve: resolves, 985 | 986 | // A user supplied controller OR 987 | // An internally created proxy controller, if resolves were requested for a Component. 988 | controller: doResolve ? controller : undefined, 989 | 990 | // Optionally controllerAs can be specifically set for those situations, 991 | // when we use @State on a class and there is no @Component defined. 992 | controllerAs: _common.common.ng2nOptions.hasOwnProperty('controllerAs') && !target.hasOwnProperty('selector') ? _common.common.ng2nOptions.controllerAs : undefined, 993 | 994 | // onEnter and onExit events 995 | onEnter: options.onEnter, 996 | onExit: options.onExit, 997 | 998 | // Custom parent State 999 | parent: options.parent, 1000 | 1001 | // Custom data 1002 | data: options.data 1003 | }; 1004 | 1005 | // sdo's template 1006 | if (options.templateUrl || options.templateProvider) { 1007 | sdo.template = undefined; 1008 | } else if (options.template) { 1009 | sdo.template = options.template; 1010 | } 1011 | 1012 | // sdo's controller 1013 | if (userController) { 1014 | sdo.controller = userController; 1015 | } 1016 | 1017 | // sdo's controllerAs 1018 | if (target.controllerAs) { 1019 | sdo.controllerAs = target.controllerAs; 1020 | } else if (options.controllerAs) { 1021 | sdo.controllerAs = options.controllerAs; 1022 | } 1023 | 1024 | // Create the state 1025 | $stateProvider.state(options.name, sdo); 1026 | 1027 | // When our automatic controller is used, we inject the resolved values into it, 1028 | // along with the injectable service that will be used to publish them. 1029 | // If the user supplied a controller than we do not inject anything 1030 | if (doResolve) { 1031 | deps.unshift(resolvedServiceName); 1032 | 1033 | controller.$inject = deps; 1034 | } 1035 | 1036 | // Populate the published service with the resolved values 1037 | function controller() { 1038 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 1039 | args[_key] = arguments[_key]; 1040 | } 1041 | 1042 | // This is the service that we "unshifted" earlier 1043 | var localScope = args[0]; 1044 | 1045 | args = args.slice(1); 1046 | 1047 | // Now we copy the resolved values to the service. 1048 | // This service can be injected into a component's constructor, for example. 1049 | deps.slice(1).forEach(function (v, i) { 1050 | localScope[v] = args[i]; 1051 | }); 1052 | } 1053 | }]); 1054 | return target; 1055 | }; 1056 | } 1057 | 1058 | /***/ }, 1059 | /* 14 */ 1060 | /***/ function(module, exports, __webpack_require__) { 1061 | 1062 | 'use strict'; 1063 | 1064 | Object.defineProperty(exports, '__esModule', { 1065 | value: true 1066 | }); 1067 | exports.options = options; 1068 | exports.Options = Options; 1069 | 1070 | var _common = __webpack_require__(1); 1071 | 1072 | var _setModule = __webpack_require__(2); 1073 | 1074 | // Allow configuration of some angular2-now default settings 1075 | // controllerAs: if provided, will user this string instead of component name, for example "vm" 1076 | 1077 | function options(options) { 1078 | if (!options) { 1079 | return _common.common.ng2nOptions; 1080 | } 1081 | 1082 | if (typeof options.controllerAs !== 'undefined') { 1083 | _common.common.controllerAs = options.controllerAs; 1084 | } 1085 | 1086 | // Optional spinner object can be registered. It must expose show() and hide() methods. 1087 | // The spinner will be activated before any I/O operations and deactivated once they complete. 1088 | _common.common.ng2nOptions.spinner = options.spinner || { 1089 | show: angular.noop, 1090 | hide: angular.noop 1091 | }; 1092 | 1093 | // events expose beforeCall() and afterCall(). 1094 | // beforeCall() will be called before any I/O operations. 1095 | // afterCall() will be called after any I/O operations have completed. 1096 | _common.common.ng2nOptions.events = options.events || { 1097 | beforeCall: angular.noop, 1098 | afterCall: angular.noop 1099 | }; 1100 | 1101 | // The noConflict option allows us to control whether or not angular2-now 1102 | // monkey-patches angular.module. 1103 | // true = don't monkey patch. 1104 | // false = (default for versions < 0.4.0) DO monkey patch angular.module 1105 | // for backwards compatibility 1106 | if (typeof options.noConflict !== 'undefined') { 1107 | angular.module = options.noConflict ? _common.common.angularModule : _setModule.SetModule; 1108 | } 1109 | } 1110 | 1111 | function Options(options) { 1112 | return function OptionsTarget(target) { 1113 | angular.merge(_common.common.ng2nOptions, options); 1114 | return target; 1115 | }; 1116 | } 1117 | 1118 | /***/ }, 1119 | /* 15 */ 1120 | /***/ function(module, exports, __webpack_require__) { 1121 | 1122 | 'use strict'; 1123 | 1124 | Object.defineProperty(exports, '__esModule', { 1125 | value: true 1126 | }); 1127 | exports.MeteorMethod = MeteorMethod; 1128 | 1129 | var _common = __webpack_require__(1); 1130 | 1131 | // The name of the Meteor.method is the same as the name of class method. 1132 | 1133 | function MeteorMethod(_options) { 1134 | var options = angular.merge({}, _common.common.ng2nOptions, _options); 1135 | var spinner = options.spinner || { 1136 | show: angular.noop, 1137 | hide: angular.noop 1138 | }; 1139 | var events = options.events || { 1140 | beforeCall: angular.noop, 1141 | afterCall: angular.noop 1142 | }; 1143 | 1144 | return function MeteorMethodTarget(target, name, descriptor) { 1145 | // Create a method that calls the back-end 1146 | descriptor.value = function () { 1147 | var argv = Array.prototype.slice.call(arguments); 1148 | var deferred = _common.common.$q.defer(); 1149 | 1150 | if (typeof spinner === 'string') { 1151 | if (angular.injector(['ng', _common.common.currentModule]).has(options.spinner)) { 1152 | spinner = angular.injector(['ng', _common.common.currentModule]).get(options.spinner); 1153 | options.spinner = spinner; 1154 | } else { 1155 | throw new Error('Spinner "' + spinner + '" does not exist.'); 1156 | } 1157 | } 1158 | 1159 | argv.unshift(name); 1160 | argv.push(resolver); 1161 | 1162 | if (spinner) { 1163 | spinner.show(); 1164 | } 1165 | 1166 | if (events.beforeCall) { 1167 | events.beforeCall(); 1168 | } 1169 | // Call optional events.beforeCall() 1170 | 1171 | // todo: should call Meteor after resolution of promise returned by beforeCall() 1172 | Meteor.call.apply(this, argv); 1173 | 1174 | deferred.promise['finally'](function () { 1175 | spinner.hide(); 1176 | // Call optional events.afterCall() 1177 | if (events.afterCall) { 1178 | events.afterCall(); 1179 | } 1180 | }); 1181 | 1182 | return deferred.promise; 1183 | 1184 | function resolver(err, data) { 1185 | if (err) { 1186 | deferred.reject(err); 1187 | } else { 1188 | deferred.resolve(data); 1189 | } 1190 | } 1191 | }; 1192 | 1193 | return descriptor; 1194 | }; 1195 | } 1196 | 1197 | /***/ }, 1198 | /* 16 */ 1199 | /***/ function(module, exports) { 1200 | 1201 | // Turn on an indication to run $reactive(this).attach($scope) for the component's controller. 1202 | // Uses with Angular-Meteor: http://angular-meteor.com, v1.3 and up only 1203 | "use strict"; 1204 | 1205 | Object.defineProperty(exports, "__esModule", { 1206 | value: true 1207 | }); 1208 | exports.MeteorReactive = MeteorReactive; 1209 | 1210 | function MeteorReactive(target) { 1211 | target.meteorReactive = true; 1212 | return target; 1213 | } 1214 | 1215 | /***/ }, 1216 | /* 17 */ 1217 | /***/ function(module, exports) { 1218 | 1219 | "use strict"; 1220 | 1221 | Object.defineProperty(exports, "__esModule", { 1222 | value: true 1223 | }); 1224 | exports.LocalInjectables = LocalInjectables; 1225 | 1226 | function LocalInjectables(target) { 1227 | target.localInjectables = true; 1228 | return target; 1229 | } 1230 | 1231 | /***/ } 1232 | /******/ ]) 1233 | }); 1234 | ; --------------------------------------------------------------------------------