Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
559 views
in Technique[技术] by (71.8m points)

angular - Class Interface function definition - TypeError: Object doesn't support property or method

I've written a construct similar to the following in an Angular app (this has been greatly simplified to demonstrate the issue). What would prevent the filterProperty() function from being defined on the item instance inside of the DemoSource class?

export keyword is used because each construct is defined in a separate file.

export interface IProperty {
    filterProperty(): string;
}

export class Demo implements IProperty {
    displayName: string;

    filterProperty(): string {
        return this.displayName;
    }
}

export class DemoSource<TItem extends IProperty> {
    filterChange = new BehaviorSubject('');
    filteredData: TItem[];

    constructor(private service: IService<TItem>) {
        // A BehaviorSubject<Array<TItem>> from a service
        this.filteredData = service.data.value.slice();
    }

    connect(): Observable<TItem[]> {
        return Observable.merge(this.service.data).map(() => {
            this.filteredData = this.service.data.value.slice().filter((item: TItem) => {
                // Object doesn't support property or method 'filterProperty'
                const searchStr = item.filterProperty().toLowerCase();
                return searchStr.indexOf(this.filter.toLowerCase()) !== -1;
            });

            return filteredData;
        });
    }
}

When debugging at the point where item.filter() is called, I get the following error:

ERROR TypeError: Object doesn't support property or method 'filterProperty'

Update
Changed the IProperty contract function from filter() to filterProperty() to avoid confusion.

Here is the Error:

error

Here, you can see how the item instance has all of its properties properly populated, but has no filterProperty() function defined (it's not in proto either):

item

Update
Here are the service details:

@Injectable()
export class IdentityService implements IService<AppUser> {
    users = new BehaviorSubject<Array<AppUser>>([]);
    public get data(): BehaviorSubject<AppUser[]> { return this.users; }
}

export interface IService<T> {
    data: BehaviorSubject<T>;
}

Here is the service being populated with data from the API:
service

Here is the result of a pure API call from the browser:
api

Certain properties have been redacted because of their data

Update - Transpiled JavaScript

Object.defineProperty(exports, "__esModule", { value: true });
var Demo = (function () {
    function Demo() {}
    Object.defineProperty(Demo.prototype, "filter", {
        get: function () { return this.displayName; },
        enumerable: true,
        configurable: true
    });
    return Demo;
}());
exports Demo = Demo;

Update
Web App demonstrating the issue: Typescript / Web API Interface Issue
GitHub Repo for the Web App: typescript-interface-issues

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Data results from JSON response and is a structure of plain objects and arrays that only have methods that are defined on their prototypes, Object and Array.

item isn't really a class instance and doesn't have filterProperty method it's supposed to have. So it's incorrect to specify DemoSource<IProperty>, considering that IProperty is supposed to have filterProperty. This fools TypeScript into thinking that objects have this method, while they don't have it - they are still plain objects, specified types don't change them.

An interface that is used for generic is supposed to reflect data structure properties (not methods). For classes that are supposed to be constructed from plain objects it's a good practice to accept plain object in constructor:

export interface IItemData {
    displayName: string;
    id?: number;
    ...
}

export class Item implements IItemData {
    displayName: string;

    constructor({ displayName }: IItemData) {
        this.displayName = displayName;
    }

    filterProperty(): string {
        return this.displayName;
    }
}

Then data structure should be processed and plain items should be converted to Item instances:

export class DemoSource<TItem extends IItemData> {
    ...
    this.filteredData = this.service.data.value.slice()
    .map((item: TItem) => {
        // item doesn't have 'filterProperty'
        return new Item(item); 
    })
    .filter((item: Item) => {
        // item has 'filterProperty'
        const searchStr = item.filterProperty().toLowerCase();
        return searchStr.indexOf(this.filter.toLowerCase()) !== -1;
    });
    ...

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...