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

匿名 (未验证) 提交于 2019-12-03 08:46:08

问题:

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" /> 

回答1:

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.



回答2:

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); } 


回答3:

I just wanted to share a similar example using the Moment library to format dates either in a controller or in the front end.

export interface IMomentFilter extends ng.IFilterService {     (name: 'momentFilter'): (dateString: string, format: string) => string; }  export class MomentFilter {     public static Factory() {         return function (dateString: string, format: string) {             return moment(dateString).format(format);         }     } } 

This is how it can be used in the controller formatting a date using the ag-grid cellRenderer:

var columnDefs = [ {     headerName: "Reported Date",      field: "reportedDate",      cellRenderer: function (params)      {         return $filter('momentFilter')(params.value, "MM/DD/YYYY");     } }]; 

This is how it can be used on the front-end:

<div class="col-xs-6 col-sm-6 col-md-6 col-lg-6">Created Date:     <label class="field">     <label class="field">{{vm.model.reportedDate| momentFilter:'MM/DD/YYYY' }}</label>     </label> </div> 

I hope this helps. Thanks a lot for the original post. I was really scratching my head to get the right syntax using the IFilterService interface.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!