Says who? You just defined your intermediate format.
export interface Block {
flavor: 'title' | 'subtitle' | 'content' | 'img' | 'list' | 'listitem' | 'boxout' | 'links' | 'link';
contents: string | Block[];
description?: string;
}
export interface Product {
blocks: Block[];
}
products:Product[] = [
{blocks: [
{flavor: 'title', contents: 'Title'},
{flavor: 'subtitle', contents: 'Subtitle'},
{flavor: 'content', contents: 'Content'},
{flavor: 'img', contents: 'http://url/img.png'},
{flavor: 'content': contents: 'Content'},
{flavor: 'subtitle', contents: 'Subtitle'},
{flavor: 'list', contents: [{flavor: 'listitem', contents: 'li1'}]},
]},
{blocks: [
{flavor: 'title', contents: 'Title'},
{flavor: 'subtitle', contents: 'SubTitle'},
{flavor: 'list', contents: [{flavor: 'listitem', contents: 'li1'}]},
{flavor: 'boxout', contents: 'Boxout'},
{flavor: 'img', contents: 'http://url/img.png'},
{flavor: 'subtitle', contents: 'Subtitle'},
{flavor: 'content', contents: 'Content'},
{flavor: 'links', contents: [{flavor: 'link', contents: 'http//url', description: 'cool link'}]},
]},
];
<ion-card *ngFor="let product of products">
<ng-template ngFor let-block [ngForOf]="product.blocks">
<h1 *ngIf="block.flavor === 'title'">{{block.contents}}</h1>
<h2 *ngIf="block.flavor === 'subtitle'">{{block.contents}}</h2>
<span *ngIf="block.flavor === 'contents'">{{block.contents}}<span>
<img [src]="block.contents" *ngIf="block.flavor === 'img'">
<ul *ngIf="block.flavor === 'list'">
<li *ngFor="let li of block.contents">{{li.contents}}</li>
</ul>
<div class="boxout" *ngIf="block.flavor === 'boxout'">{{block.contents}}</div>
<div class="links" *ngIf="block.flavor === 'links'">
<div *ngFor="let link of block.contents">
<a [href]="block.contents">{{block.description}}</a>
</div>
</div>
</ng-template>
</ion-card>