1

In my Angular project I have a list of reports. The list/array has a base class because the list can contain different types of reports. But although the list is of type: ReturnReport the actuall items in the list can be of type: SafetyReport or DraftSafetyReport. The reason for these 2 objects is that they contain different data but needed to be shown in 1 and the same datatable.

export abstract class ReturnReport {

}

export class DraftSafetyReport extends ReturnReport {

}

export class SafetyReport extends ReturnReport {
    id?: string;
    status?: string;
    safetyReportSummary!: SafetyReportSummary;
}

In my component I have the following so both types of reports can be in the list:

  reportList$: BehaviorSubject<ReturnReport[]> = new BehaviorSubject<ReturnReport[]>([]);

So far so good, and I understand how it all works, but I'm facing an issue when rendering in my HTML.

<tbody *ngIf="(reportList$ | async) as reportlist" class="">
            <tr *ngFor="let report of reportlist" class="h-12">
                <td>{{report.safetyReportSummary.occurrenceDate | date: 'dd-MM-yyyy'}}</td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
        </tbody>

The error is:

Property 'safetyReportSummary' does not exist on type 'ReturnReport'.

I know why, because the type of the list is ReturnReport. So the property safetyReportSummary does not exist. But how do I achieve this? Should I look in the corner of ng-template or ng-templateOutlet? Do I somehow cast the object before I render a specific HTML

I use Angular 15.2.3.

Any questions, please let me know! Thank you!

Leroy Meijer
  • 1,169
  • 17
  • 40

2 Answers2

0

Since the objects have a DraftSafetyReport | SafetyReport type, there's no elegant workaround I don't think.

You could create a new function that gets the value you want based on the attribute

getValue(attribute: string, obj: SafetyReport | DraftSafetyReport): any {
    if (attribute in obj) {
        return obj[attribute];
    }
}

Other options would be to create a pipe that casts the object for you, or use the type any instead, but neither are not my favorite options

  • After some more search I came accross: https://stackoverflow.com/questions/58584555/render-dynamic-template-based-on-type What is your opinion about something like this? See answer. – Leroy Meijer Mar 23 '23 at 07:04
  • I do have to add a //@ts-ignore above: obj[attribute] => Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'SafetyReport | DraftSafetyReport'. No sure why, though – Leroy Meijer Mar 23 '23 at 08:33
  • 1
    The getTypeName function from the answer seems good enough to be honest. I tried something more generic, but either way looks good to me. With that approach you may have to create more functions for other attributes, but still, it's a good answer to me. It also fixes the any type issue – eddiefba Mar 24 '23 at 15:12
0

You could use a helper function that checks the type of your list elements:

.ts

 getSafetyReportSummary(report: SafetyReport | DraftSafetyReport) {
    const _temp = report as SafetyReport
    if (_temp.safetyReportSummary) {
      return _temp.safetyReportSummary.occurrenceDate
    }
    else return null
  }

.html

<tbody *ngIf="(reportList$ | async) as reportlist" class="">
    <tr *ngFor="let report of reportlist" class="h-12">
        <td *ngIf="getSafetyReportSummary(report)">Date: {{getSafetyReportSummary(report) | date: 'dd-MM-yyyy'}}</td>
        <td></td>
        <td></td>
        <td></td>
    </tr>
</tbody>

example:

safetyReport = {
    id: "id",
    status: "status",
    safetyReportSummary: {
      occurrenceDate: Date(),
    }

  }
  draftSafetyReport = {
    name: "test"
  }

  reportList$: BehaviorSubject<ReturnReport[]> = new BehaviorSubject<ReturnReport[]>([this.safetyReport, this.draftSafetyReport]);

output:

Kostas Nitaf
  • 428
  • 1
  • 2
  • 12