Skip to content

Commit

Permalink
Merge pull request #14 from readdle/directive-decorator/feature
Browse files Browse the repository at this point in the history
Simple Directive decorator
  • Loading branch information
Alexandr Rodik authored Oct 26, 2017
2 parents 789a9d5 + 8911eae commit 55971a3
Show file tree
Hide file tree
Showing 30 changed files with 453 additions and 24 deletions.
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,61 @@ export class PlaygroundComponent implements ng.IController {
```


## Directive
Decorator for class, which links class to directive contoller.
It also passes property `selector` as a directive selector.

Lifecycle hooks:
- **ngOnInit** - links to $onInit
- **ngAfterViewInit** - links to $postLink
- **ngOnChanges** - links to $onChanges
- **ngOnDestroy** - links to $onDestroy

```typescript
import {Directive} from "ng1-shift";

@Directive({
   selector: `.ngClassDirective`,
})
export class PlaygroundDirective implements ng.IController {
ngOnInit() {
}

ngAfterViewInit() {
}

ngOnChanges() {
}

ngOnDestroy() {
}
}
```
Equals to:
```typescript
export directiveInstance() {
return {
controller: PlaygroundDirective,
restrict: "C"
}
}

class PlaygroundDirective implements ng.IController {
$onInit() {
}

$postLink() {
}

$onChanges() {
}

$onDestroy() {
}
}
```


## Input
Property decorator for bindings. Literary puts binding property name into static object `bindings` as one-way binding "<".

Expand Down
5 changes: 5 additions & 0 deletions decorators/directive/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function DirectiveFactory(type: any) {
return () => Object({
...type
});
}
24 changes: 24 additions & 0 deletions decorators/directive/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
type RestrictString = "A" | "E" | "C";

export function removeBracketsAndDot(str: string): string {
return str
.replace(/^\[|\]$/ig, "")
.replace(/^\./i, "");
}

export function getRestrictFromSelector(selector: string): RestrictString {
const firstSign = selector.substr(0, 1);
let restrict: RestrictString = "E";

switch (firstSign) {
case "[":
restrict = "A";
break;

case ".":
restrict = "C";
break;
}

return restrict;
}
44 changes: 44 additions & 0 deletions decorators/directive/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {Metakeys} from "../../models/metakeys";
import {DeclarationType} from "../../models/declaration-type";
import {getRestrictFromSelector} from "./helpers";
import kebabCaseToCamelCase from "../../helpers/kebab-case-to-camel-case";

export function Directive<IComponentClass>({selector}: {
selector?: string
}): ClassDecorator {
return (target: any) => {
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;
target.controllerAs = target.controllerAs ? target.controllerAs : target.name;
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;
}
}
16 changes: 16 additions & 0 deletions decorators/directive/interfaces.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Type of the Component decorator / constructor function.
*/
export interface Directive {
selector: string;
template?: string;
templateUrl?: string;
}

/**
* Type of the Component decorator / constructor function.
*/
export interface DirectiveDecorator {
(obj: Directive): any;
new (obj: Directive): Directive;
}
27 changes: 24 additions & 3 deletions decorators/ng-module/metadata-handlers/declaration.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
import kebabCaseToCamelCase from "../../../helpers/kebab-case-to-camel-case";
import {DeclarationType} from "../../../models/declaration-type";
import {Metakeys} from "../../../models/metakeys";
import {removeBracketsAndDot} from "../../directive/helpers";
import {DirectiveFactory} from "../../directive/factory";

export default function daclarationHandler(ng1Module: any, declarations: any) {
declarations.forEach((declaration: any) => {
const selectorNg2 = declaration.selector;
const selectorNg1 = kebabCaseToCamelCase(selectorNg2);
const hasNg1Meta = Reflect.hasMetadata(Metakeys.type, declaration);

ng1Module.component(selectorNg1, declaration);
if (hasNg1Meta) {
const declarationType = Reflect.getMetadata(Metakeys.type, declaration);
const selectorNg2 = declaration.selector;
let selectorNg1: string;

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;
}
}
});
}
4 changes: 4 additions & 0 deletions example/config/webpack.ng1.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ module.exports = Object.assign({}, baseConfig, {
exprContextCritical: false
},

devServer: {
port: 3000
},

plugins: [
new HtmlWebpackPlugin({
template: "./index.ejs",
Expand Down
4 changes: 4 additions & 0 deletions example/config/webpack.ng2.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,9 @@ module.exports = Object.assign({}, baseConfig, {
]
},

devServer: {
port: 3001
},

plugins
});
30 changes: 15 additions & 15 deletions example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions example/src/app/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const {Component} = require("./export-switch");
template: `
<ng-shift-module-component></ng-shift-module-component>
<ng-shift-component></ng-shift-component>
<ng-shift-directive></ng-shift-directive>
<ng-shift-router></ng-shift-router>
`
})
Expand Down
28 changes: 28 additions & 0 deletions example/src/app/directive/attribute/directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const {Directive, EventEmitter, Input, Output} = require("../../export-switch");

@Directive({
selector: "[ng-shift-attr-directive]"
})
export class NgShiftAttrDirective {
@Input() ngShiftDirectiveProp?: string;
@Output() ngShiftDirectiveOutput = new EventEmitter();

ngOnInit() {
console.log(this.constructor.name, this.ngShiftDirectiveProp);

const titleSpan = document.createElement("span");
titleSpan.textContent = this.constructor.name;

document.querySelector("ng-shift-directive > div").appendChild(titleSpan);

setTimeout(() => this.ngShiftDirectiveOutput.emit(), 1000);
}

ngAfterViewInit() {
console.log("ngAfterViewInit", this.ngShiftDirectiveProp);
}

ngOnChanges() {
console.log("onChanges", this.constructor.name);
}
}
24 changes: 24 additions & 0 deletions example/src/app/directive/class/directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const {Directive, EventEmitter, Input, Output} = require("../../export-switch");

@Directive({
selector: ".ng-shift-class-directive"
})
export class NgShiftClassDirective {
@Input() ngShiftDirectiveProp?: string;
@Output() ngShiftDirectiveOutput = new EventEmitter();

ngOnInit() {
console.log(this.constructor.name);

const titleSpan = document.createElement("span");
titleSpan.textContent = `, ${this.constructor.name}`;

document.querySelector("ng-shift-directive > div").appendChild(titleSpan);

setTimeout(() => this.ngShiftDirectiveOutput.emit(), 1000);
}

ngOnChanges() {
console.log("onChanges", this.constructor.name);
}
}
20 changes: 20 additions & 0 deletions example/src/app/directive/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const {Component, EventEmitter, Output} = require("../export-switch");
const template = process.env.NG2 ? require("./template.ng2.html") : require("./template.ng1.html");

@Component({
selector: "ng-shift-directive",
template: template
})
export class NgShiftDirectiveComponent {
propValue = "propValue";

ngOnInit() {
setTimeout(() => {
this.propValue = "propValue2";
}, 2000);
}

goDirective(id: string) {
console.log("Output: directive", id);
}
}
24 changes: 24 additions & 0 deletions example/src/app/directive/element/directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const {Directive, EventEmitter, Input, Output} = require("../../export-switch");

@Directive({
selector: "ng-shift-elem-directive"
})
export class NgShiftElemDirective {
@Input() ngShiftDirectiveProp?: string;
@Output() ngShiftDirectiveOutput = new EventEmitter();

ngOnInit() {
console.log(this.constructor.name);

const titleSpan = document.createElement("span");
titleSpan.textContent = `, ${this.constructor.name}`;

document.querySelector("ng-shift-directive > div").appendChild(titleSpan);

setTimeout(() => this.ngShiftDirectiveOutput.emit(), 1000);
}

ngOnChanges() {
console.log("onChanges", this.constructor.name);
}
}
17 changes: 17 additions & 0 deletions example/src/app/directive/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const {NgModule, CommonModule} = require("../export-switch");

import {NgShiftDirectiveComponent} from "./component";
import {NgShiftAttrDirective} from "./attribute/directive";
import {NgShiftElemDirective} from "./element/directive";
import {NgShiftClassDirective} from "./class/directive";

@NgModule({
declarations: [
NgShiftDirectiveComponent,
NgShiftAttrDirective,
NgShiftElemDirective,
NgShiftClassDirective
],
exports: [ NgShiftDirectiveComponent ]
})
export class NgShiftDirectiveModule {}
Loading

0 comments on commit 55971a3

Please sign in to comment.