I am experimenting with the .component()
syntax in Angular 1.5.
It seems that the latest fashion is to code the controller in-line in the component rather t
I am using a simple Typescript decorator to create the component
function Component(moduleOrName: string | ng.IModule, selector: string, options: {
controllerAs?: string,
template?: string,
templateUrl?: string
}) {
return (controller: Function) => {
var module = typeof moduleOrName === "string"
? angular.module(moduleOrName)
: moduleOrName;
module.component(selector, angular.extend(options, { controller: controller }));
}
}
so I can use it like this
@Component(app, 'testComponent', {
controllerAs: 'ct',
template: `
<pre>{{ct}}</pre>
<div>
<input type="text" ng-model="ct.count">
<button type="button" ng-click="ct.decrement();">-</button>
<button type="button" ng-click="ct.increment();">+</button>
</div>
`
})
class CounterTest {
count = 0;
increment() {
this.count++;
}
decrement() {
this.count--;
}
}
You can try a working jsbin here http://jsbin.com/jipacoxeki/edit?html,js,output
I'd suggest not to use custom made solutions, but to use the ng-metadata
library instead. You can find it at https://github.com/ngParty/ng-metadata. Like this your code is the most compatible with Angular 2 possible. And as stated in the readme it's
No hacks. No overrides. Production ready.
I just switched after using a custom made solution from the answers here, but it's easier if you use this library right away. Otherwise you’ll have to migrate all the small syntax changes. One example would be that the other solutions here use the syntax
@Component('moduleName', 'selectorName', {...})
while Angular 2 uses
@Component({
selector: ...,
...
})
So if you're not using ng-metadata
right away, you'll considerably increase the effort of migrating your codebase later on.
A full example for the best practice to write a component would be the following:
// hero.component.ts
import { Component, Inject, Input, Output, EventEmitter } from 'ng-metadata/core';
@Component({
selector: 'hero',
moduleId: module.id,
templateUrl: './hero.html'
})
export class HeroComponent {
@Input() name: string;
@Output() onCall = new EventEmitter<void>();
constructor(@Inject('$log') private $log: ng.ILogService){}
}
(copied from ng-metadata recipies)
I'm using the following pattern to use angular 1.5 component with typescript
class MyComponent {
model: string;
onModelChange: Function;
/* @ngInject */
constructor() {
}
modelChanged() {
this.onModelChange(this.model);
}
}
angular.module('myApp')
.component('myComponent', {
templateUrl: 'model.html',
//template: `<div></div>`,
controller: MyComponent,
controllerAs: 'ctrl',
bindings: {
model: '<',
onModelChange: "&"
}
});
If you wanted to completely adopt an Angular 2 approach, you could use:
module.ts
import { MyComponent } from './MyComponent';
angular.module('myModule', [])
.component('myComponent', MyComponent);
MyComponent.ts
import { Component } from './decorators';
@Component({
bindings: {
prop: '<'
},
template: '<p>{{$ctrl.prop}}</p>'
})
export class MyComponent {
prop: string;
constructor(private $q: ng.IQService) {}
$onInit() {
// do something with this.prop or this.$q upon initialization
}
}
decorators.ts
/// <reference path="../typings/angularjs/angular.d.ts" />
export const Component = (options: ng.IComponentOptions) => {
return controller => angular.extend(options, { controller });
};
This is the pattern I use:
ZippyComponent.ts
import {ZippyController} from './ZippyController';
export class ZippyComponent implements ng.IComponentOptions {
public bindings: {
bungle: '<',
george: '<'
};
public transclude: boolean = false;
public controller: Function = ZippyController;
public controllerAs: string = 'vm';
public template: string = require('./Zippy.html');
}
ZippyController.ts
export class ZippyController {
bungle: string;
george: Array<number>;
static $inject = ['$timeout'];
constructor (private $timeout: ng.ITimeoutService) {
}
}
Zippy.html
<div class="zippy">
{{vm.bungle}}
<span ng-repeat="item in vm.george">{{item}}</span>
</div>
main.ts
import {ZippyComponent} from './components/Zippy/ZippyComponent';
angular.module('my.app', [])
.component('myZippy', new ZippyComponent());
I believe one good approach is to use angular-ts-decorators. With it you can define Components in AngularJS like this:
import { Component, Input, Output } from 'angular-ts-decorators';
@Component({
selector: 'myComponent',
templateUrl: 'my-component.html
})
export class MyComponent {
@Input() todo;
@Output() onAddTodo;
$onChanges(changes) {
if (changes.todo) {
this.todo = {...this.todo};
}
}
onSubmit() {
if (!this.todo.title) return;
this.onAddTodo({
$event: {
todo: this.todo
}
});
}
}
and then register them in your module using:
import { NgModule } from 'angular-ts-decorators';
import { MyComponent } from './my-component';
@NgModule({
declarations: [MyComponent]
})
export class MyModule {}
If you want to check an example of a real application using it, you can check this one.