Angular + Typescript code convention - angularjs

I am using a combination of Angularjs + Typescript for my project. I am searching for a good code convention. There are some some good examples for the two technologies separately. For example I follow this one when using TypeScript:
https://github.com/Platypi/style_typescript#introduction
But couldn't find a good one for the combination of Angularjs + Typescript. To be a little bit more specific, for example, I need a convention on how to write directives with the typescript syntax etc.
I could not find any good articles on the topic. If someone can share something on the topic it will be great. Thanks!

I have followed this projects structure when writing angularjs + typescript.
Since directives are actually factory functions writing directives will be done in the same way:
module Directives{
export function MyDirective(optionalService): ng.IDirective{
var myDirective = {};
myDirective.restrict = 'A';
myDirective.link = function(scope, elem){};
//etc
return myDirective;
}
MyDirective.$inject = ["optionalService"];
}
app.directive("myDirective", Directives.MyDirective);

If you use Angular1.5+ you can follow Angular2 Component style:
class DemoController implements ng.IComponentController {
public static $inject = [
'Service1',
];
constructor(private Service1: IService1) {
//
}
public $onInit(): void {
// ---
}
public $postLink(): void {
// ---
}
// ---
}
export const DemoComponent = {
selector: 'demoComponent',
bindings: {
prop1: '<',
prop2: '<',
},
controller: DemoController,
templateUrl: require('./your-tpl.html'),
};
And somewhere in your module, you can register this component:
.component(DemoComponent.selector, DemoComponent)
For creating directives this code style is also pretty nice:
export class MyDemoDirective implements ng.IDirective {
public restrict: string;
public static $inject: string[] = [
'Service1',
'Service2',
];
constructor(private Service1: IService1, private Service2: IService2) {
this.restrict = 'A';
// ---
}
public link(...) {
// ---
}
public static factory(): ng.IDirectiveFactory {
const directive = (Service1: IService1, Service2: IService2) => {
return new MyDemoDirective($filter, User);
};
return directive;
}
}

Related

AngularJS (1.5) dependency injection into models classes (typescript)

I am trying to use typescript to build an angularjs (1.5) application.
I want to make the equivalent of a factory that calls a model (which can take parameters).
I could do the following if I were to do it in pure JS
angular.module('test', [])
.factory('Talker', [$q, function($q){
return Talker()
function Talker(name) {
this.name = name || 'Bobuel Johnson';
}
Talker.prototype.deferredHello = function(){
$q.when('Hi, I\'m ' + this.name);
}
}])
Now I want to do this in typescript such that I can have the Talker class but I want to be able to inject (in this case) $q into the model class.
Please, can you help me figure out how to get this set up as the typescript equivalent?
I would recommend using a service recipe instead of a factory, if only to prepare for angular2. A TypeScript rewrite of that js code would look something like this.
class Talker {
public static $inject = ['$q']; // configure angular di
private name: string = 'Some talker';
constructor(private $q) {}
public deferredHello() {
this.$q.when('Hi, I\'m ' + this.name);
}
}
angular.module('test').service('Talker', Talker);
You can find the translation to js here.
If a factory is absolutely needed you should be able to do that as well like this:
class Talker {
private name: string = 'Some talker';
constructor(private $q) {}
public deferredHello() {
this.$q.when('Hi, I\'m ' + this.name);
}
}
angular.module('test').service('Talker', function($q) {
return new Talker($q);
});
You can check out the generated javascript here.
If you need to create multiple instances of Talker you can do it this way.
class Talker {
private name: string = 'Some talker';
constructor(private $q, money: number) {}
public deferredHello() {
this.$q.when('Hi, I\'m ' + this.name);
}
}
angular.module('test').service('Talker', function($q) {
// we can ask for more parameters if needed
return function talkerFactory(money) { // return a factory instead of a new talker
return new Talker($q, money);
};
});
angular.module('yatest').service('Organiser', function(Talker) {
let talker = Talker(42); // will call talkerFactory and return a new instance of talker
})
The example for that is here.

How to load dynamic directive content using AngularJS? [duplicate]

This question already has answers here:
Compiling dynamic HTML strings from database
(5 answers)
Closed 6 years ago.
I'm having a hard time trying to implement a dynamic loaded content using Angular... I have several widgets and their partialName variable is loaded via binding, after a request. That's why I'm using scope.$watch below. Even though the template is set with its correct content, the view itself doesn't refresh to display the appropriate content. I've tried somethings like the $broadcast below with no luck. How can I achieve this? Thanks!
export class CioTemplateLoader implements ng.IDirective {
public template: string;
public partials: { [id: string]: string; } = {};
public scope = {
partialName: '='
};
public route: any;
static $inject = [
'$route'
];
constructor(){//$route: any) {
// this.route = $route;
this.partials['claimOverview'] = require('../../claims/claim-overview.html');
//...
}
link = (scope: ng.IScope, elem: JQuery, attributes: ng.IAttributes) => {
var that = this;
scope.$watch('partialName', function (value: string) {
if (value) {
that.template = that.partials[value];
debugger;
scope.$broadcast("REFRESH");
}
});
}
public static Factory(): angular.IDirectiveFactory {
var directive = () => {
return new CioTemplateLoader();
};
return directive;
}
}
what you want to do is compile dynamically html templates and render them, right? If so, this question might be marked as duplicate as for example Compiling dynamic HTML strings from database

Custom Angular validator in TypeScript

I've created a custom angular form validator in TypeScript. Everything works fine on the browser but typescript is complaining that "Property 'compareTo' does not exist on type 'IModelValidators'" at this line:
ngModel.$validators.compareTo = modelValue => (modelValue === scope.otherModelValue); Which makes sense, because I am basically creating a new validator called "comparedTo" that doesn't exist and just attaching it to the model. This is totally valid in javascript but since Typescript is strongly typed it didn't like it that much. Does anyone have any idea of how to add my "compareTo" validation to the ngModel.$validators in a typescript safe way? Thanks!
'use strict';
module App.Directives {
export interface ILoZScope extends angular.IScope {
otherModelValue: string;
}
export class CompareToDirective implements angular.IDirective {
// Directive parameters.
public restrict = 'A';
public require = 'ngModel';
public scope = { otherModelValue: '=compareTo' }
// Constructor
public static $inject = ['$window'];
constructor($window) {}
// Link function
public link(scope: ILoZScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes, ngModel: angular.INgModelController) {
ngModel.$validators.compareTo = modelValue => (modelValue === scope.otherModelValue);
scope.$watch('otherModelValue', () => { ngModel.$validate(); });
}
// Creates an instance of the compareToDirective class.
public static factory(): angular.IDirectiveFactory {
const directive = ($window: angular.IWindowService) => new CompareToDirective($window);
directive.$inject = ['$window'];
return directive;
}
}
angular
.module('app')
.directive('compareTo', CompareToDirective.factory());
}
If you just want to skip the typescript error, just create a custom definitely typed file and add something like this.
interface IModelValidators {
comparedTo: any;
}
If you want to get proper intellisense, use something like this in your custom d.ts file.
interface IModelValidators {
comparedTo: (modelValue: any, viewValue: any) => boolean;
}
an alternate solution would be
ngModel.$validators["compareTo"] = (modelValue, viewValue) : boolean => {
if(modelValue....) {
return true
}
return false;
}

Directive with Typescript and isolate scope function binding

I am writing a web app with AngularJS and, for the first time, using TypeScript. The issue I'm having is with a directive executing a function passed on its isolate scope.
I have a controller that is managing "work" of different types. I then have different directives to manage each type of work. The controller reserves the next piece of work, then places it on the scope for the appropriate directive. The controller is below.
module app.work {
interface IWorkScope {
workDone: boolean;
workCompleted(): void;
}
export class WorkController implements IWorkScope {
currentReservedWork: app.work.IReservedWork;
static $inject = ['app.work.WorkService', '$log'];
constructor(private workService: app.work.IWorkService,
private $log: ng.ILogService) {
}
private getNextWork() {
//...call service, reserve work, prep data...
}
public workCompleted(): void {
//...any cleanup tasks, reserve next piece of work....
}
}
angular.module('app.work')
.controller('app.work.WorkController', WorkController);
}
The directives then handling the actual workflow of executing the "work" of its type. An example is below.
module app.verify {
'use strict';
export interface IVerifyItemScope extends ng.IScope {
reserved: app.work.IReservedWork;
onItemComplete(): any;
saveButtonClicked(): void;
item: app.verify.IVerifyItem;
vm: app.verify.IVerifyItemController;
}
export interface IVerifyItemController {
getVerifyItem(): void;
}
class VerifyItemController implements IVerifyItemController{
item: app.verify.IVerifyItem;
statuses: app.verify.VerifyResultStatus[];
tempBind: number;
static $inject = ['$scope', 'app.verify.VerifyService', '$log'];
constructor(public $scope: IVerifyItemScope,
private verifyService: app.verify.IVerifyService,
private $log: ng.ILogService) {
}
saveButtonClicked(): void {
this.$log.debug('button clicked');
this.$scope.onItemComplete();
}
getVerifyItem(): void {
//...call service to get specific work details...
}
}
angular
.module('app.verify')
.directive('sfVerifyItem', verifyItem);
function verifyItem(): ng.IDirective {
var directive = <ng.IDirective> {
restrict: 'E',
link: link,
templateUrl: '....',
controller: VerifyItemController,
controllerAs: 'vm',
scope: {
reserved: '=',
onItemComplete: '&'
}
};
function link(scope: any, element: ng.IAugmentedJQuery, attributes: any, controller: IVerifyItemController): void {
//...do any prep...
}
return directive;
}
}
<data-sf-verify-item reserved="vm.currentReservedWork" onItemComplete="vm.workCompleted()"></data-sf-verify-item>
When the user compeletes the "work", the directive will do any necessary service calls. Last, it executes function onItemComplete that was passed on the scope to inform the controller that the work is done, then the controller can reserve more work and the process repeats.
The issue I'm having is the function bound on the scope isn't getting executed. I can debug and see it on the isolate scope, but when I execute it in the directive (saveButtonClicked() above), nothing happens in the parent controller.
Sorry for the long post, but any help or insight would be greatly appreciated.
See Hugues comment above, forgot to convert binding from camel case.

Implementing angularjs directives as classes in Typescript

So after taking a look at some of the examples of angularjs directives in typescript, it seems most people agree to use functions instead of classes when implementing them.
I would prefer to have them as a class and attempted to implement them as follows:
module directives
{
export class search implements ng.IDirective
{
public restrict: string;
public templateUrl: string;
constructor()
{
this.restrict = 'AE';
this.templateUrl = 'directives/search.html';
}
public link($scope: ng.IScope, element: JQuery, attributes: ng.IAttributes)
{
element.text("Hello world");
}
}
}
Now this works fine. However, I need to have an isolated scope with some attributes and I'm struggling to find out how to include that in the class itself.
logic dictates that since I can have
public restrict: string;
public templateUrl: string;
I should be able to have something like:
public scope;
But I'm not sure if this is correct or how to carry on from there (i.e how to add the attributes to the scope).
Anybody know how to solve this? (hopefully, without having to revert to a function if possible)
Thanks,
Creating directives as classes can be problematic since you still need to involve a factory function to wrap its instantiation. For example:
export class SomeDirective implements ng.IDirective {
public link = () => {
}
constructor() {}
}
What Doesn't Work
myModule.directive('someDirective', SomeDirective);
Since directives are not invoked using 'new' but are just called as factory functions. This will cause problems on what your constructor function actually returns.
What Does (with Caveats)
myModule.directive(() => new SomeDirective());
This works fine provided you don't have any IoC involved, but once you start introducing injectables, you have to maintain duplicate parameter lists for your factory function and your directive contstructor.
export class SomeDirective implements ng.IDirective {
...
constructor(someService: any) {}
}
myModule.directive('someDirective', ['someService', (someService) => new SomeDirective(someService)]);
Still an option if that is what you prefer, but is important to understand how the directive registration is actually consumed.
An alternative approach
The thing that is actually expected by angular is a directive factory function, so something like:
export var SomeDirectiveFactory = (someService: any): ng.IDirective => {
return {
link: () => {...}
};
}
SomeDirectiveFactory.$inject = ['someService']; //including $inject annotations
myModule.directive('someDirective', SomeDirectiveFactory);
This has the benefit of allowing the use of $inject annotations since angular needs it to be on the factory function in this case.
You could always return an instance of your class from the factory function as well:
export var SomeDirectiveFactory = (someService: any): ng.IDirective => {
return new SomeDirective(someService);
}
SomeDirectiveFactory.$inject = ['someService']; //including $inject annotations
But really depends on your use case, how much duplication of parameter lists you are okay with, etc.
Assuming that what you have works without an islolated scope, the following should work with an isolated scope:
module directives
{
export class search implements ng.IDirective
{
public restrict = 'AE';
public templateUrl = 'directives/search.html';
public scope = {
foo:'=',
bar:'#',
bas:'&'
};
public link($scope: ng.IScope, element: JQuery, attributes: ng.IAttributes)
{
element.text("Hello world");
}
}
}
Here is my proposal:
Directive:
import {directive} from '../../decorators/directive';
#directive('$location', '$rootScope')
export class StoryBoxDirective implements ng.IDirective {
public templateUrl:string = 'src/module/story/view/story-box.html';
public restrict:string = 'EA';
public scope:Object = {
story: '='
};
public link:Function = (scope:ng.IScope, element:ng.IAugmentedJQuery, attrs:ng.IAttributes):void => {
// console.info(scope, element, attrs, this.$location);
scope.$watch('test', () => {
return null;
});
};
constructor(private $location:ng.ILocationService, private $rootScope:ng.IScope) {
// console.log('Dependency injection', $location, $rootScope);
}
}
Module (registers directive...):
import {App} from '../../App';
import {StoryBoxDirective} from './../story/StoryBoxDirective';
import {StoryService} from './../story/StoryService';
const module:ng.IModule = App.module('app.story', []);
module.service('storyService', StoryService);
module.directive('storyBox', <any>StoryBoxDirective);
Decorator (adds inject and produce directive object):
export function directive(...values:string[]):any {
return (target:Function) => {
const directive:Function = (...args:any[]):Object => {
return ((classConstructor:Function, args:any[], ctor:any):Object => {
ctor.prototype = classConstructor.prototype;
const child:Object = new ctor;
const result:Object = classConstructor.apply(child, args);
return typeof result === 'object' ? result : child;
})(target, args, () => {
return null;
});
};
directive.$inject = values;
return directive;
};
}
I thinking about moving module.directive(...), module.service(...) to classes files e.g. StoryBoxDirective.ts but didn't make decision and refactor yet ;)
You can check full working example here: https://github.com/b091/ts-skeleton
Directive is here: https://github.com/b091/ts-skeleton/blob/master/src/module/story/StoryBoxDirective.ts
Here finally i got working a directive as class plus inheritance. In derived directive I extend the scope plus define the templateUrl.
You can override any methods from base directive
.
The key was to return from constructor the instance of directive.Angularjs calls constructor without new keyword. In this case this is of type window
I wrapped few lines to check the instance type of this and in case of window I create a new instance of directive. (See Activator class from sample below)
module Realty.directives {
export class BaseElementWithLabel implements ng.IDirective {
public restrict = 'E';
public replace = true;
public scope = {
label: '#',
model: '=',
icon: '#',
readonlyElement: '=',
remark: '#'
}
constructor(extendedScope) {
if (!(this instanceof Window)) {
extendedScope = extendedScope || {};
this.scope = angular.extend(this.scope, extendedScope);
}
}
link(scope: ng.IScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes, controller, transclude) {
scope['vm'] = {};
}
}
}
module Realty.directives {
export class textareaWithLabel extends Realty.directives.BaseElementWithLabel implements ng.IDirective {
templateUrl = 'directives/element-form/textarea-with-label-template.html';
constructor() {
super({
rows: '#'
});
return Realty.Activator.Activate(this, textareaWithLabel, arguments);
}
};
};
module Realty {
export class Activator {
public static Activate(instance: any, type, arguments) {
if (instance instanceof type) {
return instance;
}
return new(type.bind.apply(type, Array.prototype.concat.apply([null], arguments)));
}
}
}

Resources