Objective
I am attempting to encapsulate the formatting logic (not routing logic) into a component.
I am using the Angular Material’s MatList component.
<mat-nav-list>
<!-- The below <a> has some common routing and styling logics -->
<a
mat-list-item
[class.active]="isActive"
routerLinkActive="active2"
[activated]="isActive"
[routerLink]="link"
>
<!-- Control formatting -->
@if(singleLine) {
<app-list-single-line-format [item]="item" />
} @else {
<app-list-multi-line-format [item]="item" />
}
</a>
</mat-nav-list>
It is almost working. But the styling is broken when used as a component.
Minimal reproduction
<mat-nav-list role="list">
<!-- working -->
<a mat-list-item role="listitem">
<mat-icon matListItemIcon>folder</mat-icon>
<div matListItemTitle>hello</div>
<div matListItemLine>world</div>
</a>
<!-- Broken styles -->
<a mat-list-item role="listitem">
<app-list-item />
</a>
</mat-nav-list>
Styling of the second item is broken. I don’t want to fix this by hacky css. Is there a proper way? Stackblitz
Excerpt:
@Component({
selector: "app-list-item",
template: `
<mat-icon matListItemIcon>folder</mat-icon>
<div matListItemTitle>hello</div>
<div matListItemLine>world</div>
`,
standalone: true,
imports: [MatListModule, MatIconModule, RouterLink],
})
export class ListItem {}
@Component({
selector: "list-overview-example",
template: `
<mat-nav-list role="list">
<!-- working -->
<a mat-list-item role="listitem">
<mat-icon matListItemIcon>folder</mat-icon>
<div matListItemTitle>hello</div>
<div matListItemLine>world</div>
</a>
<!-- Broken styles -->
<a mat-list-item role="listitem">
<app-list-item />
</a>
</mat-nav-list>
`,
standalone: true,
imports: [MatListModule, MatIconModule, RouterLink, ListItem],
})
export class ListOverviewExample {}
Here is the demo: https://stackblitz.com/edit/3ruxqq?file=src%2Fexample%2Flist-overview-example.html
2
Unfortunately, the way that you wrap those elements into a separate component is not working when applying it to the mat-list-item
element.
mat-list-item
has its own structure:
<ng-content select="[matListItemAvatar],[matListItemIcon]"></ng-content>
<span class="mdc-list-item__content">
<ng-content select="[matListItemTitle]"></ng-content>
<ng-content select="[matListItemLine]"></ng-content>
<span #unscopedContent class="mat-mdc-list-item-unscoped-content"
(cdkObserveContent)="_updateItemLines(true)">
<ng-content></ng-content>
</span>
</span>
...
With the additional component, this results in those elements located in the nested HTML element (<app-list-item>
), and MatList
unable to query for <mat-icon>
, matListItemTitle
and matListItemLine
elements correctly.
Instead, all the elements will be rendered in the <ng-content>
part resulting in the styles not working well.
Thus, would suggest you don’t need the additional component if that component is just for display and without any additional purpose. Just place the HTML structure within the mat-list-item
.
<mat-nav-list>
<!-- The below <a> has some common routing and styling logics -->
<a
mat-list-item
[class.active]="isActive"
routerLinkActive="active2"
[activated]="isActive"
[routerLink]="link"
>
<!-- Control formatting -->
@if(singleLine) {
<mat-icon matListItemIcon>{{ /* item.icon */ }}</mat-icon>
<div matListItemTitle>{{ /* item.title */ }</div>
<div matListItemLine>{{ /* item.line */ }</div>
} @else {
<!-- HTML Structure from <app-list-multi-line-format> -->
}
</a>
</mat-nav-list>
Otherwise, you need to move the <a mat-list-item>
to the ListItem
component. This requires you to add the @Input
binding in the component to receive the data such as isActive
, link
and etc.