angulartypescriptionic-frameworkrxjs

TS2740: Type '{}' is missing the following properties from type 'ExpenseModel[]': length, pop, push, concat, and 26 more


[ERROR] TS2740: Type '{}' is missing the following properties from type 'ExpenseModel[]': length, pop, push, concat, and 26 more. [plugin angular-compiler]

    src/app/modules/expenses/pages/expenses-by-date/expenses-by-date.component.html:7:7:
      7 │       [expensesByDate]="expenses"
        ╵        ~~~~~~~~~~~~~~

  Error occurs in the template of component ExpensesByDatePageComponent.

    src/app/modules/expenses/pages/expenses-by-date/expenses-by-date.component.ts:27:15:
      27 │   templateUrl: './expenses-by-date.component.html',
         ╵                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

expenses-by-date.component.html page

@if (expensesChanged$ | async; as expenses) {
  <ng-container>
    <app-expenses-by-date
      [expensesByDate]="expenses"
      (expenseDetailsChange)="updateExpense($event)"
    ></app-expenses-by-date>
  </ng-container>
  }

expenses-by-date.component.ts page

expensesChanged$: Observable<ExpenseModel[]> = of([] as ExpenseModel[]);

 ngOnInit(): void {
     this.expensesChanged$ = this.expenseDataService.expensesChanged$.pipe(
      startWith([]),
      map((res: ExpenseModel[]) => filter(res, (f) => f.date === Number(date)))
    );
  }

expense-data.service.ts

constructor() {
     this.expensesSubject = new BehaviorSubject<ExpenseModel[]>([]);
    this.expensesChanged$ = this.expensesSubject.asObservable();
  }

package.json

{
  "name": "",
  "version": "1.5.24",
  "author": "",
  "homepage": "",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "lint": "ng lint"
  },
  "private": true,
  "dependencies": {
    "@angular/cdk": "20.1.5",
    "@angular/common": "20.1.6",
    "@angular/core": "20.1.6",
    "@angular/forms": "20.1.6",
    "@angular/platform-browser": "20.1.6",
    "@angular/platform-browser-dynamic": "20.1.6",
    "@angular/router": "20.1.6",
    "@capacitor-community/sqlite": "5.0.0",
    "@capacitor-firebase/authentication": "5.1.0",
    "@capacitor/android": "5.2.3",
    "@capacitor/app": "5.0.0",
    "@capacitor/camera": "5.0.0",
    "@capacitor/core": "5.2.3",
    "@capacitor/filesystem": "5.2.1",
    "@capacitor/haptics": "5.0.0",
    "@capacitor/ios": "5.2.3",
    "@capacitor/keyboard": "5.0.0",
    "@capacitor/network": "5.0.0",
    "@capacitor/preferences": "5.0.0",
    "@capacitor/splash-screen": "5.0.0",
    "@capacitor/status-bar": "5.0.0",
    "@ionic/angular": "8.7.2",
    "@sentry/angular": "^9.38.0",
    "@sentry/capacitor": "2.1.0",
    "@types/lodash": "4.14.178",
    "dayjs": "1.10.7",
    "firebase": "9.6.10",
    "guid-typescript": "1.0.9",
    "lodash": "4.17.21",
    "native-run": "^2.0.1",
    "rxjs": "7.5.0",
    "tslib": "2.8.1",
    "zone.js": "0.15.1"
  },
  "devDependencies": {
    "@angular-eslint/builder": "20.1.1",
    "@angular-eslint/eslint-plugin": "20.1.1",
    "@angular-eslint/eslint-plugin-template": "20.1.1",
    "@angular-eslint/template-parser": "20.1.1",
    "@angular/build": "^20.1.5",
    "@angular/cli": "20.1.5",
    "@angular/compiler": "20.1.6",
    "@angular/compiler-cli": "20.1.6",
    "@angular/language-service": "20.1.6",
    "@capacitor/cli": "5.0.0",
    "@ionic/angular-toolkit": "^11.0.1",
    "@sentry/cli": "2.20.7",
    "@types/node": "12.11.1",
    "@typescript-eslint/eslint-plugin": "8.39.1",
    "@typescript-eslint/parser": "8.39.1",
    "eslint": "9.33.0",
    "eslint-plugin-import": "2.32.0",
    "eslint-plugin-jsdoc": "54.0.0",
    "eslint-plugin-prefer-arrow": "1.2.3",
    "prettier": "2.5.1",
    "ts-node": "8.3.0",
    "typescript": "5.8.3"
  },
  "description": ""
}

It worked with Angular 16.But after upgrading to the latest Angular, it gives the above error. If I subscribed within expenses-by-date.component.ts file without async in the HTML file, then it works. How can I solve this with async on html file, hence I have a lot of such components on the app.

expenses-by-date.component.html component like so:

<cdk-virtual-scroll-viewport
  itemSize="50"
  minBufferPx="600"
  maxBufferPx="600"
  class="virtual-scroller-viewport"
>
  <div *cdkVirtualFor="let expense of expensesByDate">
    <ion-row button (click)="openExpenseDetails(expense)">
      <ion-col size="7" class="font-size-18">
        <div>
          {{ expense.name }}
        </div>
      </ion-col>

      <ion-col>
        {{ expense.amount | currency: 'GBP' }}</ion-col
      >
    </ion-row>

    
  </div>
</cdk-virtual-scroll-viewport>

expenses-by-date.component.ts component

@Input() expensesByDate: ExpenseModel[] = [];

Solution

  • On analysis I think the filter of lodash, is causing the type to be lost. In my opinion, the loadash filter should be replaced by the filter of JavaScript, which should fix this issue.

    ngOnInit(): void {
      this.expensesChanged$ = this.expenseDataService.expensesChanged$.pipe(
        startWith([]),
        map((res: ExpenseModel[]) =>
          res.filter((f: any) => f.date === Number(this.date)) // changed here!
        )
      );
    } 
    

    Due to issue not being reproducible you can use type casting to solve this error as a workaround, but the actual problem needs to be replicated to find the proper solution.

    @if (expensesChanged$ | async; as expenses) {
      @let expensesTyped = typedValue(expensesTyped);
      <ng-container>
        <app-expenses-by-date
          [expensesByDate]="expenses"
          (expenseDetailsChange)="updateExpense($event)"
        ></app-expenses-by-date>
      </ng-container>
    }
    

    The typed method will be.

    typedValue(expenses) {
      return expenses as ExpenseModel[];
    }