I am attempting to invoke a custom filter from an Angular controller but I get the error: 'Cannot invoke an expression whose type lacks a call signature'.
I implemented it like this on the last project I worked on so I am at a loss as to what is wrong.
The filter does not contain any logic at this point as I need to get it compiling first.
Here is the filter:
/// <reference path="../../typings/reference.ts" />
module app {
'use strict';
/**
* Filter models
*/
export class ModelFilter {
public static Factory() {
return function(input: string) {
console.log(input);
return input;
}
}
}
angular.module('app')
.filter('modelFilter', [ModelFilter.Factory]);
}
And the controller where its called:
/// <reference path="../../typings/reference.ts" />
module app {
'use strict';
interface ISearchController {
vehicles: IVehicles;
models: any;
setVehicles(): void;
updateModels(make: string): void;
}
class SearchController implements ISearchController {
static $inject = [
'VehicleMakeService',
'VehicleModelService',
'$filter'
];
constructor(private vehicleMakeService: VehicleMakeService,
private vehicleModelService: VehicleModelService,
private $filter: ng.IFilterService,
public vehicles: IVehicles,
public models: any) {
this.setVehicles();
}
setVehicles(): void {
this.vehicleMakeService.getVehicles().then((data) => {
this.vehicles = data;
});
}
updateModels(make: string): void {
var test = this.$filter('modelFilter')(make); // Error here
}
}
angular.module('app').controller('SearchController', SearchController);
}
reference.ts:
/// <reference path="./tsd.d.ts" />
//grunt-start
/// <reference path="../app/app.config.ts" />
/// <reference path="../app/app.module.ts" />
/// <reference path="../app/app.route.ts" />
/// <reference path="../app/home/home.controller.ts" />
/// <reference path="../app/home/home.route.ts" />
/// <reference path="../app/models/vehicles.model.ts" />
/// <reference path="../app/results/results.controller.ts" />
/// <reference path="../app/results/results.route.ts" />
/// <reference path="../app/services/cars.service.ts" />
/// <reference path="../app/services/vehicles.make.service.ts" />
/// <reference path="../app/services/vehicles.models.service.ts" />
/// <reference path="../app/templates/search.controller.ts" />
/// <reference path="../app/templates/search.filter.ts" />
//grunt-end
tsd.d.ts:
/// <reference path="angularjs/angular.d.ts" />
/// <reference path="jquery/jquery.d.ts" />
/// <reference path="angular-ui-router/angular-ui-router.d.ts" />
/// <reference path="angularjs/angular-resource.d.ts" />
Modified working example:
/// <reference path="typings/angularjs/angular.d.ts" />
module app {
// ADDED <--- MODIFIED!
export interface MyModelFilter extends ng.IFilterService {
(name: 'modelFilter'): (input: string) => string;
}
/**
* Filter models
*/
export class ModelFilter {
public static Factory() {
return function(input: string) {
console.log(input);
return input;
}
}
}
angular.module('app')
.filter('modelFilter', [ModelFilter.Factory]);
}
module app {
class SearchController {
constructor(private $filter: MyModelFilter) { // <--- MODIFIED!
}
updateModels(make: string): void {
var test = this.$filter('modelFilter')(make);
}
}
angular.module('app').controller('SearchController', SearchController);
}
The problem is that TypeScript uses the following definition:
/**
* $filter - $filterProvider - service in module ng
*
* Filters are used for formatting data displayed to the user.
*
* see https://docs.angularjs.org/api/ng/service/$filter
*/
interface IFilterService {
(name: 'filter'): IFilterFilter;
(name: 'currency'): IFilterCurrency;
(name: 'number'): IFilterNumber;
(name: 'date'): IFilterDate;
(name: 'json'): IFilterJson;
(name: 'lowercase'): IFilterLowercase;
(name: 'uppercase'): IFilterUppercase;
(name: 'limitTo'): IFilterLimitTo;
(name: 'orderBy'): IFilterOrderBy;
/**
* Usage:
* $filter(name);
*
* @param name Name of the filter function to retrieve
*/
<T>(name: string): T;
}
for this.$filter('modelFilter')
. It means that the last rule (i.e. <T>(name: string): T;
) is used. Consequently, this.$filter('modelFilter')
is of type ng.IFilterService
and TypeScript does not know anything about your ModelFilter
.
You can solve the problem by adding a new interface as shown in the first code listing.
You said that the original code worked in another project of yours but it seems very unlikely to me unless reference.ts
was somehow modified.
If you see the last line of the IFilterService there is a generic type for custom filters.
interface IFilterService {
(name: 'filter'): IFilterFilter;
(name: 'currency'): IFilterCurrency;
(name: 'number'): IFilterNumber;
(name: 'date'): IFilterDate;
(name: 'json'): IFilterJson;
(name: 'lowercase'): IFilterLowercase;
(name: 'uppercase'): IFilterUppercase;
(name: 'limitTo'): IFilterLimitTo;
(name: 'orderBy'): IFilterOrderBy;
/**
* Usage:
* $filter(name);
*
* @param name Name of the filter function to retrieve
*/
<T>(name: string): T;
}
As the error suggests, your custom filter has no call signature. So you can create an interface for your filter which includes a call signature:
export interface MyModelFilter {
(input: string): string;
}
And then when you call your filter you can use the IFilterService's generic type to attribute your call signature to your custom filter
updateModels(make: string): void {
var test = this.$filter<MyModelFilter>('modelFilter')(make);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With