1

In my angular5 application, I am using a table. And data in the table comes from the backend. When the table is fully loaded for the first time, I want to change the scroll position inside the table. For that I am doing this:

ngAfterViewInit() {
    setTimeout(function () {
        const scroller = document.getElementById('list-container');
        scroller.scrollTop = amountTop; // amountTop is the calculated height, which represents the centre of the table 
    }, 500);
}

Is there any better way for DOM manipulation for this instead of using setTimeout() function?

4
  • put the code inside setTimeout into success callback of API call Commented Jan 3, 2018 at 9:15
  • Thanks. But I don't wanna use setTimeout() function for this operation. Because the loading might take more than 500 ms if that the case then my objective will not achieve. Is there any other better way to do this? Commented Jan 3, 2018 at 12:36
  • can u pls post the code for the API call ? Commented Jan 3, 2018 at 12:56
  • this.marketService.getBuyOrder(marketId, start, limit).subscribe(data => { const buyOrders = data; if (buyOrders.length > 0) { this.bids = this.bids.concat(buyOrders); } else { this.loadMore = false; } this.cd.markForCheck(); // marks path }, (err: HttpErrorResponse) => { Helper.handleError(err, this.alertService); }); here, bids is the array of objects from which i am populating data in a table. <tr *ngFor='let bid of bids> Commented Jan 3, 2018 at 13:01

2 Answers 2

3

You need to execute the scroll top code on success of your API call in setTimeout with timeout being 0, so that the code is executed after a tick i.e after angular is done with data binding

this.marketService.getBuyOrder(marketId, start, limit)
.subscribe(data => { 
    const buyOrders = data; 
    if (buyOrders.length > 0) { 
        this.bids = this.bids.concat(buyOrders); 
    } else { 
        this.loadMore = false; 
    } 
    this.cd.markForCheck();

    // The timeout is set to 0 to allow angular to bind the array to view
    setTimeout(function () {
        const scroller = document.getElementById('list-container');
        scroller.scrollTop = amountTop;
    }, 0);
}, (err: HttpErrorResponse) => { Helper.handleError(err, this.alertService); });
Sign up to request clarification or add additional context in comments.

1 Comment

This approach is not working perfectly on other browsers(Safari, Firefox) than Google Chrome. Could you suggest the better way to do this?
1

If you want your page to display when data is loaded, I strongly advices using route resolves. The article shows how to build and implement this, but I'll try to explain this in short.

Instead of loading the data when accessing the component the route resolve will trigger when a certain URL is accessed. This will get the data from your service and add it to the route snapshot data. From your component you can access this data via the ActivatedRoute and your component will only be loaded when the route resolve was successfully.

One thing the article doesn't explain is how to handle exceptions but here's a code snippet so you don't have to show a blank page. I'd recommend looking at this after you've red the article though.

resolve(route: ActivatedRouteSnapshot): Observable<MyObject> {
    return this.myService.getMyObjectById(route.params['id'])
        .map(myObject => {
            return myObject;
        })
        .catch(e => {
            console.error(e);
            return Observable.of([]); // You could return an other object.
            this.router.navigate(['404']); // Or you could redirect to a different page using the @angular/router
        });
}

Update

What this means in your specific case is that you can just call

ngOnInit() {
    this.myList = this.route.snapshot.data['myList']
    const scroller = document.getElementById('list-container');
    scroller.scrollTop = amountTop;
}

Now you're sure your data is present when the ngOnInit method is called because the component is only loaded when the resolve was successfully

Update 2

If you want to load your data in a async way without the route resolves you can solve it like this

ngOnInit() {
    this.myService.getMyObjectByAParameter(myParameter).subscribe(
      myList => {
          this.myList = myList;
          const scroller = document.getElementById('list-container');
          scroller.scrollTop = amountTop;
      }, err => {
          // Handle your exception
      }
  );
}

If you do it like this your component will always be loaded but the scroll will only be set after the data is successfully loaded.

5 Comments

My question was related to DOM manipulation in angular. If a do this: const scroller = document.getElementById('list-container'); in ngAfterViewInit() function I will get null value. I want to avoid this, that's why I am using setTimeout() method. But I don't think it is the best practice because response time might take more than expected. I am new to angular and I wanna know how it can be done in angular?
@Pradip I've updated my answer to explain why this is a good solution for you
Thanks. Can I pass any data from component to Resolve class which implements resolve methods? Because I need to pass some parameter which is not in the URL param to get the data from the API.
@Pradip No this can't be done because the resolve is called before the component is loaded. If you need to refresh data after your component is loaded, you'll have to access your service from the component directly
@Pradip I've added another update that contains an async example that requires less code and might suit your requirements better

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.