The technique you're looking for is called content projection. Essentially, you define a component per section that you need, then you define a wrapper component that renders these sections at the right places:
// the header section
@Component({
selector: 'app-card-header',
standalone: true,
imports: [CommonModule],
// note that it's possible to wrap this too, as it's a normal Angular component
// we're just rendering whatever was passed into the component, to keep it simple
template: '<ng-content />',
})
export class CardHeaderComponent {}
// the content section
@Component({
selector: 'app-card-content',
standalone: true,
imports: [CommonModule],
template: '<ng-content />',
})
export class CardContentComponent {}
// the footer section
@Component({
selector: 'app-card-footer',
standalone: true,
imports: [CommonModule],
template: '<ng-content />',
})
export class CardFooterComponent {}
// the wrapper component
@Component({
selector: 'app-card',
standalone: true,
imports: [CommonModule],
template: `
<!-- this means: if there was an <app-card-header> passed to the component, render it here -->
<ng-content select="app-card-header"></ng-content>
<ng-content select="app-card-content"></ng-content>
<ng-content select="app-card-footer"></ng-content>`
})
export class CardComponent {}
// this is how we use it
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, CardComponent, CardHeaderComponent, CardContentComponent, CardFooterComponent],
template: `
<app-card>
<app-card-header>
<h3>It's a kind of header</h3>
</app-card-header>
<app-card-content>
Yippie, here comes my content.
</app-card-content>
<app-card-footer>
<button>Act as you want</button>
</app-card-footer>
</app-card>`,
})
export class App {}
Demo