Moving Angular controller functions to a separate Typescript class - angularjs

I am working on an application using AngularJS and Typescript. Many requirements are being added to the application so I want to nail down the structure before the code gets out of hand.
I have an Angular controller with 1500 lines and I would like to move the functions to a separate file like so:
export class CtrlFunctions{
private scope: any;
constructor($scope) {
this.scope = $scope;
}
updateHeader = header => {
//more logic here
this.scope.header = header;
}
}
And I would access the values in the controller like this:
import { CtrlFunctions } from "../controller-functions/entry.ctrl.func";
namespace Entry {
class EntryCtrl {
private funcs: CtrlFunctions
constructor(){
this.funcs = new CtrlFunctions($scope);
}
}
}
Is this bad practice? Is there a better way to take advantage of the modularity provided by Typescript?

Related

Angular Typescript - using a custom filter inside a controller

I have a custom filter in my angular application (typescript) as below:
namespace InterACT {
export namespace Filters {
export class ContractAppliedTo {
public static onFilterApplied(contract: Interfaces.IReimbursementContract) {
if (contract.services && contract.services.length > 0) {
return _.map(contract.services, 'regNumber').join(', ');
} else if (contract.operator) {
return contract.operator.name;
}
return contract.scheme.abbreviation;
}
}
}
}
angular
.module('app.settlement')
.filter('contractAppliedTo', () => { return InterACT.Filters.ContractAppliedTo.onFilterApplied; });
I am able to use this filter just fine in my markup like other built in filters:
{{rule | contractAppliedTo}}
I now need to use this filter in a controller, and am struggling how to reference it for usage.
I already use angular date filters in some of my controllers, and in the constructor of these, I am injecting in ng.IFilterService and assigning to a private variable for usage later on.
namespace InterACT {
export namespace Controllers {
export class MyController {
public static $inject = ['$filter'];
constructor(
private $filter: ng.IFilterService
) {
}
public someFunction = () => {
let dateFilter = this.$filter('date'),
foo;
foo = dateFilter('some-date-string', 'd-M-Y');
}
}
}
}
When it comes to using my ContractAppliedTo filter, I can't get the controller to recognise it for me to use, or I guess what I mean is the typescript compiler wont pick it up.
I wondered if I could use the filter service to grab an instance of my new filter, e.g:
let contractAppliedTo = this.$filter('contractAppliedTo')
But when I try to use it, I get a typescript error saying
[ts] Cannot invoke an expression whose type lacks a call signature. Type '{}' has no compatible call signatures.
I think I am missing an interface in order to be able to use this filter in my controller.
Can someone advise further please?
Thanks
This issue is that the FilterService cannot find the call signature for your custom filter. I was able to get around the issue by importing the custom filter and using type assertion against the usage of the $filter service.
const contractAppliedTo: contractAppliedTo =
(<contractAppliedTo>this.$filter('contractAppliedTo'));

Typescript private and protected members exposed to angular 1.x view

It seems like when combining TS and angular, everything i have on a controller is exposed to the view. In my case, myPrivate will appear on $ctrl.
class MyController extends BaseController implements SomeInterface {
private myPrivate: string = 'myPrivateString';
}
Is there any workaround around this issue?
It's pretty obvious why when you look at the generated javascript.
var MyController = (function (_super) {
__extends(MyController, _super);
function MyController() {
_super.apply(this, arguments);
this.myPrivate = 'myPrivateString';
}
return MyController;
}(BaseController));
Your private property ends up as any other property on your controller.
You can see the full transpilation here.
A solution would be to have a parameterized base controller able to set something like a view model for the view to use, instead of the regular $ctrl.
It would look something like this:
class BaseController<T> {
protected scope;
protected viewModel: T;
constructor(scope: any, modelType: { new (): T; }) {
this.scope = scope;
this.viewModel = new modelType();
this.scope["viewModel"] = this.viewModel;
}
}
class MyParticularViewModel {
public somethingForTheView: string;
}
class MyController extends BaseController<MyParticularViewModel> implements SomeInterface {
private myPrivate: string = 'myPrivateString';
constructor(scope) {
super(scope, MyParticularViewModel);
}
}
In the view you can then use the viewModel property to access the needed properties.
I have used this in a project in practice and it worked out pretty well. You can see a starter template that I used here for more info.

Angular and Typescript: proper way to reference 'this'

I am setting typescript in an angular project. In order to declare a controller I use the following syntax:
module app {
class MyController {
public myvar: boolean;
constructor() {
this.myvar= false;
}
}
angular.module("app").controller("MainController", [MainController]);
}
Please note that I don't inject the scope, I only use inner properties / methods of the controller.
But I don't like to access to properties with 'this', usually I should declare:
var vm = this.
vm.myvar = ...
However this is annoying as I have many methods; I should declare this in any ones, this is repetitive.
Is there a best practice and/or a pattern, in order to declare the 'vm' only once?
But I don't like to access to properties with 'this', usually I should declare var vm = this ... Is there a best practice and/or a pattern, in order to declare the 'vm' only once?
It's a good time to drop that practice. In TypeScript it's easy to just use this and not assign this to a variable—it's already defined for you so it's nice to use it.
The key when doing this is to use arrow functions to make sure you always use the class instance's this and not the this bound to a regular function expression.
class MyController {
myVar = false;
someOtherMethod() {
// good
functionWithCallback(() => {
// this will be the class instance
console.log(this.myVar);
});
// bad
functionWithCallback(function() {
// this will not be the class instance
console.log(this.myVar);
});
// good
functionWithCallback(() => this.myOtherMethod());
// bad, `this` in myOtherMethod is not the class instance
functionWithCallback(this.myOtherMethod);
}
myOtherMethod() {
console.log(this.myVar);
}
}
function functionWithCallback(callback: Function) {
callback();
}

Setting context of "this" from another typescript class, using AngularJS dependency injection

I'm using a TypeScript class to define a controller in AngularJS:
class TrialsCtrl {
constructor(private $scope: ITrialsScope, private ResourceServices: ResourceServices) {
this.loadTrials();
}
loadTrials() {
console.log("TrialsCtrl:", this);
this.Trial.query().then((result) => {
this.$scope.Trials = result;
});
}
remove(Trial: IRestTrial) {
this.ResourceServices.remove(Trial, this.loadTrials);
}
}
angular.module("app").controller("TrialsCtrl", TrialsCtrl);
I'm refactoring common controller methods into a service.
class ResourceServices {
public remove(resource, reload) {
if (confirm("Are you sure you want to delete this?")) {
resource.remove().then(() => {
reload();
});
}
}
}
angular.module("app").service("ResourceServices", ResourceServices);
The console log shows that this is referencing the window context when I want it to be TrialsCtrl. My problem is that the reload() method needs to run in the context of TrialsCtrl, so that it can access this.Trial and this.$scope. How can I tell the reload() method to set this as the TrialsCtrl? Or is there some other workaround I should be using for this kind of thing?
Have you tried:
this.ResourceServices.remove(Trial, this.loadTrials.bind(this));
or
this.ResourceServices.remove(Trial, () => this.loadTrials());
For methods that are supposed to be passed as callbacks (as with this.loadTrials) it is preferable to define them as arrows,
loadTrials = () => { ... }
So they keep the context whether Function.prototype.bind is used or not.
Alternatively, a decorator may be used on the method (like core-decorators #autobind) to bind a method while still defining it on class prototype:
#autobind
loadTrials() { ... }

Creating a custom Angular filter with TypeScript

I'm trying to work out the best way of creating a custom Angular Filter with TypeScript.
All the code samples I see use something like:
myModule.filter( "myFilter", function()
{
return function( input )
{
// filter stuff here
return result;
}
}
... which works, but seems messy as I want to keep all my filter code separate. So I want to know how to declare the filter as a separate file (eg filters/reverse-filter.ts) so I can create it like:
myModule.filter( "filterName", moduleName.myFilter );
... the same way you would for Controllers, Services etc.
The documentation for TS and Angular together seems pretty thin on the ground, especially where filters are concerned - can anyone help out?
Cheers!
Functions can be exported from modules like this:
module moduleName {
export function myFilter()
{
return function(input)
{
// filter stuff here
return result;
}
}
}
then outside the module:
myModule.filter("filterName", moduleName.myFilter);
Then it would then be possible to do things like automatically register all of the filters defined in the moduleName module by iterating over its public properties.
Maybe too late but can be useful for someone else.
module dashboard.filters{
export class TrustResource{
static $inject:string[] = ['$sce'];
static filter($sce:ng.ISCEService){
return (value)=> {
return $sce.trustAsResourceUrl(value)
};
}
}
}
dashboard.Bootstrap.angular.filter('trustAsResourceUrl',dashboard.filters.TrustResource.filter);
To explain the last line:
dashboard.Bootstrap.angular.filter('trustAsResourceUrl',dashboard.filters.TrustResource.filter);)
I will add a piece of code, wich represents my Bootstrap class, so you can understand it.
module dashboard {
export class Bootstrap {
static angular:ng.IModule;
static start(){
Bootstrap.angular = angular.module('EmbApp', dashboard.Bootstrap.$inject);
}
}
}
//run application
dashboard.Bootstrap.start();
If you need more information about how it works, you can checkout my own TypeScript/AngularJS/Less structure here
Here's an example using the injector to get dependencies into your filter. This one gets injected with the $filter service.
export class CustomDateFilter {
public static Factory() {
var factoryFunction = ($filter: ng.IFilterService) => {
var angularDateFilter = $filter('date');
return (theDate: string) => {
return angularDateFilter(theDate, "yyyy MM dd - hh:mm a");
};
};
factoryFunction.$inject = ['$filter'];
return factoryFunction;
}
}
// and in the bootstrap code:
app.filter('customDate', your.module.CustomDateFilter.Factory());
You should use something like this to inject dependencies
myModule.filter('filterName', ['$http', moduleName.myFilter]);
You can create a filter using class with a static function.
export class FilterClass
{
static id = "FilterId"; //FilterName, use while consume
/*#ngInject*/
public static instance() { //static instance function
let dataFilter = () => {
let filteredObject = () => {
//filter logic
return filteredData;
}
return filteredObject;
}
return dataFilter;
}
}
//Module configuration
angular.module(myModule).filter(FilterClass.id, FilterClass.instance());
Consume this filter in the controller using below way.
let FilterFun:any = this.$filter('FilterId');
let Filteroutput = FilterFun();

Resources