ng1-shift
Version:
Angular 1.5+ decorators for writing Angular2 like.
395 lines (374 loc) • 14.9 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["ng1-shift"] = {}));
})(this, (function (exports) { 'use strict';
const Metakeys = {
type: "ng1-shift:type",
componentOptions: "ng1-shift:componentOptions"
};
class ComponentMetadataService {
constructor(target) {
this.target = target;
}
addInput(componentProp, inputName) {
const options = this.metadata;
options.inputs.push({ componentProp, inputName });
this.metadata = options;
}
addOutput(componentProp, outputName) {
const options = this.metadata;
options.outputs.push({ componentProp, outputName });
this.metadata = options;
}
getInputs() {
return this.metadata.inputs;
}
getOutputs() {
return this.metadata.outputs;
}
get hasMetadata() {
return Reflect.hasMetadata(Metakeys.componentOptions, this.target);
}
get metadata() {
if (this.hasMetadata) {
return Reflect.getMetadata(Metakeys.componentOptions, this.target);
}
return {
inputs: [],
outputs: []
};
}
set metadata(value) {
Reflect.defineMetadata(Metakeys.componentOptions, value, this.target);
}
}
function findFirst(array, expression) {
// @ts-ignore
if (!angular.isFunction(expression)) {
const first = array[0];
if (!first) {
return null;
}
return first;
}
for (let i = 0; i < array.length; i++) {
if (expression(array[i])) {
return array[i];
}
}
return null;
}
// Solution from: https://stackoverflow.com/questions/30758961/how-to-check-if-a-variable-is-an-es6-class-declaration#answer-68708710
function isClass(value) {
return typeof value === "function" && (/^\s*class[^\w]+/.test(value.toString()) ||
// 1. native classes don't have `class` in their name
// 2. However, they are globals and start with a capital letter.
(window[value.name] === value && /^[A-Z]/.test(value.name)));
}
function wrapRouterConfig(routerConfig) {
if (isClass(routerConfig)) {
const wrapperFunction = (...args) => {
// @ts-ignore
return new routerConfig(...args);
};
wrapperFunction.$inject = routerConfig.$inject;
return wrapperFunction;
}
return routerConfig;
}
function importHandler(imports) {
let ng1ModuleIds = [];
const modules = imports.filter((mdl) => {
return !(mdl.$inject
&& findFirst(mdl.$inject, (i) => i === "$stateProvider"));
});
const ng1RouterConfig = findFirst(imports, (mdl) => {
return mdl.$inject
&& findFirst(mdl.$inject, (i) => i === "$stateProvider");
});
if (modules.length) {
ng1ModuleIds = modules.map((mdl) => {
if (mdl.ng1ShiftModuleName) {
return mdl.ng1ShiftModuleName;
}
return mdl;
});
}
return {
ng1ModuleIds,
ng1RouterConfig: wrapRouterConfig(ng1RouterConfig),
};
}
function kebabCaseToCamelCase(str) {
if (str.indexOf("-") !== -1) {
const newArr = str.toLowerCase().split("-");
// We are starting from 1 because the first letter in camelCase word is in lowercase
for (let i = 1; i < newArr.length; i++) {
newArr[i] = newArr[i].charAt(0).toUpperCase() + newArr[i].substr(1);
}
return newArr.join("");
}
return str;
}
const DeclarationType = {
component: "component",
directive: "directive"
};
function removeBracketsAndDot(str) {
return str
.replace(/^\[|\]$/ig, "")
.replace(/^\./i, "");
}
function getRestrictFromSelector(selector) {
const firstSign = selector.substr(0, 1);
let restrict = "E";
switch (firstSign) {
case "[":
restrict = "A";
break;
case ".":
restrict = "C";
break;
}
return restrict;
}
function DirectiveFactory(type) {
return () => Object(Object.assign({}, type));
}
function daclarationHandler(ng1Module, declarations) {
declarations.forEach((declaration) => {
const hasNg1Meta = Reflect.hasMetadata(Metakeys.type, declaration);
if (hasNg1Meta) {
const declarationType = Reflect.getMetadata(Metakeys.type, declaration);
const selectorNg2 = declaration.selector;
let selectorNg1;
switch (declarationType) {
case DeclarationType.component:
selectorNg1 = kebabCaseToCamelCase(selectorNg2);
ng1Module.component(selectorNg1, declaration);
break;
case DeclarationType.directive:
selectorNg1 = removeBracketsAndDot(selectorNg2);
ng1Module.directive(selectorNg1, DirectiveFactory(declaration));
break;
}
}
});
}
function providerHandler(ng1Module, providers, declarations) {
declarations.forEach((declaration) => {
const injections = Reflect.getMetadata("design:paramtypes", declaration);
if (injections) {
const injectedServices = injections.map(({ name }) => name);
if (!declaration.$inject) {
declaration.$inject = [];
}
providers.forEach((provider) => {
const serviceToken = provider.name;
const injectIndex = injectedServices.indexOf(serviceToken);
declaration.$inject[injectIndex] = serviceToken;
ng1Module.service(serviceToken, provider);
});
}
});
}
const entityCounters = {};
function counter(entity) {
const registeredEntity = entity in entityCounters;
if (!registeredEntity) {
entityCounters[entity] = 0;
}
return entityCounters[entity]++;
}
function NgModule({ id, imports, declarations, providers, directRegister }) {
return function (target) {
let ng1ModuleIds = [];
let ng1RouterConfig;
const hasImports = imports && imports.length;
if (hasImports) {
const handledImports = importHandler(imports);
ng1ModuleIds = handledImports.ng1ModuleIds;
ng1RouterConfig = handledImports.ng1RouterConfig;
}
target.ng1ShiftModuleName = `${target.name}-${counter("moduleName")}`;
if (id === "app-module") {
target.ng1ShiftModuleName = id;
}
// @ts-ignore
const ng1Module = angular.module(target.ng1ShiftModuleName, ng1ModuleIds);
if (ng1RouterConfig) {
ng1Module.config(ng1RouterConfig);
}
if (declarations && declarations.length) {
daclarationHandler(ng1Module, declarations);
}
if (providers && providers.length) {
providerHandler(ng1Module, providers, declarations);
}
if (directRegister) {
directRegister(ng1Module);
}
};
}
function replaceTwoWayBindings(target) {
const componentMetadata = new ComponentMetadataService(target), inputs = componentMetadata.getInputs(), outputs = componentMetadata.getOutputs();
if (inputs.length > 0 && outputs.length > 0) {
inputs.forEach(input => {
const requiredOutput = `${input.inputName}Change`;
const hasOutput = outputs.some(output => output.outputName === requiredOutput);
if (hasOutput && target.bindings) {
target.bindings[input.componentProp] = target.bindings[input.componentProp].replace(/^</, "=");
}
});
}
}
function Component(config) {
return function (target) {
if (config) {
if (config.template) {
target.template = config.template;
}
if (config.selector) {
target.selector = config.selector;
}
}
// Lifecycle hooks aliases
if (target.prototype.ngOnInit) {
target.prototype.$onInit = target.prototype.ngOnInit;
}
if (target.prototype.ngAfterViewInit) {
target.prototype.$postLink = target.prototype.ngAfterViewInit;
}
if (target.prototype.ngOnChanges) {
target.prototype.$onChanges = target.prototype.ngOnChanges;
}
if (target.prototype.ngOnDestroy) {
target.prototype.$onDestroy = target.prototype.ngOnDestroy;
}
// Controller linking
target.controller = target;
replaceTwoWayBindings(target);
Reflect.defineMetadata(Metakeys.type, DeclarationType.component, target);
return target;
};
}
function Directive({ selector }) {
return (target) => {
if (selector) {
target.selector = kebabCaseToCamelCase(selector);
}
// Lifecycle hooks aliases
if (target.prototype.ngOnInit) {
target.prototype.$onInit = target.prototype.ngOnInit;
}
if (target.prototype.ngAfterViewInit) {
target.prototype.$postLink = target.prototype.ngAfterViewInit;
}
if (target.prototype.ngOnChanges) {
target.prototype.$onChanges = target.prototype.ngOnChanges;
}
if (target.prototype.ngOnDestroy) {
target.prototype.$onDestroy = target.prototype.ngOnDestroy;
}
// Controller linking
target.restrict = getRestrictFromSelector(target.selector);
target.controller = target;
// IE11 doesn't support function.name
target.controllerAs = target.controllerAs || target.name || target.selector;
target.bindToController = target.bindings ? target.bindings : {};
// always isolated scope, unless set other
target.scope = target.scope !== void 0 ? target.scope : false;
Reflect.defineMetadata(Metakeys.type, DeclarationType.directive, target);
return target;
};
}
function Input(alias) {
return function (target, property) {
const componentMetadata = new ComponentMetadataService(target.constructor);
if (!target.constructor.bindings) {
target.constructor.bindings = {};
}
const propName = String(property);
const attrBinding = alias ? alias : propName;
target.constructor.bindings[propName] = "<" + attrBinding;
componentMetadata.addInput(propName, attrBinding);
};
}
function Output(alias) {
return function (target, property) {
const componentMetadata = new ComponentMetadataService(target.constructor);
if (!target.constructor.bindings) {
target.constructor.bindings = {};
}
const propName = String(property);
const privateCallbackName = `__${propName}`;
const attrBinding = alias ? alias : propName;
target.constructor.bindings[privateCallbackName] = `&${attrBinding}`;
componentMetadata.addOutput(propName, attrBinding);
Object.defineProperty(target, privateCallbackName, {
set: function (callback) {
if (!this.__callbackCache) {
this.__callbackCache = {};
}
if (typeof callback === "function") {
this.__callbackCache[propName] = callback;
}
},
enumerable: false,
configurable: true
});
Object.defineProperty(target, propName, {
set: function (eventEmitterInstance) {
if (!this.__eventEmitterCache) {
this.__eventEmitterCache = {};
}
if (eventEmitterInstance && eventEmitterInstance.subscribe) {
eventEmitterInstance.subscribe((eventData) => {
if (typeof this.__callbackCache[propName] === "function") {
this.__callbackCache[propName]({ $event: eventData });
}
});
this.__eventEmitterCache[propName] = eventEmitterInstance;
}
},
get: function () {
return this.__eventEmitterCache[propName];
},
enumerable: true,
configurable: true
});
};
}
function Inject(dependencyName) {
return function (target, property, parameterIndex) {
if (!target.$inject) {
target.$inject = [];
}
const injectionToken = typeof dependencyName === "string"
? dependencyName
: dependencyName.$injectionToken;
target.$inject[parameterIndex] = injectionToken;
};
}
class EventEmitter {
constructor() {
this.listeners = [];
}
emit(event) {
this.listeners.forEach(callback => callback.call(null, event));
}
subscribe(callback) {
this.listeners.push(callback);
}
}
exports.Component = Component;
exports.Directive = Directive;
exports.EventEmitter = EventEmitter;
exports.Inject = Inject;
exports.Input = Input;
exports.NgModule = NgModule;
exports.Output = Output;
Object.defineProperty(exports, '__esModule', { value: true });
}));
//# sourceMappingURL=index.js.map