Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to invoke custom filter results in 'error TS2349: Cannot invoke an expression whose type lacks a call signature'

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" />
like image 680
AngularBoy Avatar asked Oct 10 '15 21:10

AngularBoy


2 Answers

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.

like image 136
Martin Vseticka Avatar answered Oct 06 '22 21:10

Martin Vseticka


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);
}
like image 2
Romesh Panditha Avatar answered Oct 06 '22 21:10

Romesh Panditha