I have an Angular Material list with multiple elements, where one of them can be selected.
When I load this list I want to scroll list up to that selected element, to make it more visible. Is there any option?
I was thinking about something like in ngOnInit to check if item is selected, but I have really no idea how to scroll this list up to that item.
Note: not whole page should be scrolled, only the elements on the list.
component.html
<mat-nav-list>
<mat-list-item *ngFor="let item of items" (click)="itemClick(item)"
[ngClass]="item.selectedClass">
{{item.someValue}}
</mat-list-item>
</mat-nav-list>
component.ts
private itemClick(item: Item): Observable<any> {
if (item) {
this.something = item;
this.items.forEach(item => {
if (item && item.name === item.name) {
item["selectedClass"] = "item-selected";
} else {
item["selectedClass"] = undefined;
}
});
} else {
this.something = null;
this.items.forEach(item => {
item["selectedClass"] = undefined;
});
}
return of(null);
}
Check out this demo: https://stackblitz.com/edit/angular-yjbpoq?file=src%2Fapp%2Fhello.component.ts
The gist is that you attach a new directive to your list and to each of your list items:
<mat-list appScrollable ...>
<mat-list-item appOffsetTop ...>
...
</mat-list-item>
</mat-list>
The one on the list can set the scrollTop
(or use scrollTo
, or whatever) for the list:
@Directive({selector: '[appScrollable]'})
export class ScrollableDirective {
constructor(private _el: ElementRef) {}
set scrollTop(value: number) { this._el.nativeElement.scrollTop = value; }
}
And the one on the list items can report their offsetTop
s:
@Directive({ selector: '[appOffsetTop]'})
export class OffsetTopDirective {
constructor(private _el: ElementRef) { }
get offsetTop(): number { return this._el.nativeElement.offsetTop; }
}
Each type of directive is accessed in the component via @ViewChild
and @ViewChildren
:
@ViewChildren(OffsetTopDirective) listItems: QueryList<OffsetTopDirective>;
@ViewChild(ScrollableDirective) list: ScrollableDirective;
In the AfterViewInit
life cycle hook, the list of items is searched for the one that matches the current selected item (set randomly here for illustration purposes). The list's scrollTop
is then set to that item's offsetTop
:
// Inside your component
selectedItem = Math.floor(Math.random() * 500);
items = new Array(500).fill(0).map((_, i) => `Item ${i}`);
ngAfterViewInit() {
this.list.scrollTop = this.listItems.find((_, i) => i === this.selectedItem).offsetTop;
}
You can use the directive approach with scrollIntoView:
@Directive({
selector: '[appScrollIntoView]'
})
export class ScrollIntoViewDirective implements OnChanges {
@Input() appScrollIntoView: boolean | undefined;
constructor(
@Inject(PLATFORM_ID) private platformId: any,
private elementRef: ElementRef
) {}
ngOnChanges(simpleChange: SimpleChanges) {
if (isPlatformBrowser(this.platformId)) {
if (coerceBooleanProperty(this.appScrollIntoView)) {
(this.elementRef.nativeElement as HTMLInputElement).scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
}
}
}
usage:
<mat-list class="list" role="list">
<mat-list-item
class="list-item"
[class.selected]="item === selectedItem"
[appScrollIntoView]="item === selectedItem"
*ngFor="let item of items"
(click)="selectedItem = item"
role="listitem"
>{{ item.name }}</mat-list-item
>
</mat-list>
stackblitz
If you use the material selection list the MatListOption has a focus method which is another option.
@Directive({
selector: 'mat-list-option[appScrollIntoView]'
})
export class ScrollIntoViewDirective implements OnChanges {
@Input() appScrollIntoView: boolean | undefined;
constructor(
@Inject(PLATFORM_ID) private platformId: any,
private elementRef: ElementRef,
private matListOption: MatListOption
) {}
ngOnChanges(simpleChange: SimpleChanges) {
if (isPlatformBrowser(this.platformId)) {
if (coerceBooleanProperty(this.appScrollIntoView)) {
this.matListOption.focus();
}
}
}
}
usage:
<mat-selection-list
(selectionChange)="selectedItem = $event.option.value"
class="list"
[multiple]="false"
role="list"
#itemList
>
<mat-list-option
class="list-item"
[appScrollIntoView]="option.selected"
*ngFor="let item of items"
role="listitem"
[selected]="item === selectedItem"
[value]="item"
#option
>{{ item.name }}</mat-list-option
>
</mat-selection-list>
stackblitz
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With