Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Material Angular scroll to element on mat-list

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);
    }
like image 823
OldShaterhan Avatar asked Jan 22 '19 14:01

OldShaterhan


2 Answers

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 offsetTops:

@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;
  }
like image 195
night_owl Avatar answered Nov 15 '22 12:11

night_owl


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

like image 33
JayChase Avatar answered Nov 15 '22 11:11

JayChase