8

My ngAfterViewInit function is not working as expected. I have feeling that I am missing something here.

  ngOnInit() {
   this.dataService.getUsers().subscribe((users) => {this.users = users) ;  
  }

  ngAfterViewInit() {
    if (document.getElementById('target')) {
        document.getElementById('target').scrollIntoView({
          behavior: 'auto',
          block: 'center',
          inline: 'center',
       });
   }

So basically, I'm using ngAfterViewInint() function to scroll to the current item, after the view complete loaded. Inside ngOnInit(), if I am not calling subscribe() and just put a variable there. It works just fine. But its not the case that what i am asking.

I have a work around by using setTimeOut() inside ngAfterViewInit(). But I don't like it. I dont like the fact that its relies on timeout. And I think there would be a better solution for it. Any ideal ? Thanks all.

Muhammed Albarmavi
  • 23,240
  • 8
  • 66
  • 91
daniel8x
  • 990
  • 4
  • 16
  • 34

3 Answers3

5

It looks like your code depends on your dataService.getUsers() call returning before ngAfterViewInit() gets invoked?

If so, that's not going to work, because there's no guarantee about the timing. Instead, do this:

 ngAfterViewInit() {
   this.dataService.getUsers().subscribe((users) => {
      this.users = users;
      this.scrollTargetIntoView();
   )};
 }

  scrollTargetIntoView() {
    if (document.getElementById('target')) {
        document.getElementById('target').scrollIntoView({
          behavior: 'auto',
          block: 'center',
          inline: 'center',
       });
   }

Even this may be a bit too early - you may want to add a setTimeout() to this to give the DOM a chance to be rendered, e.g.:

ngAfterViewInit() {
   this.dataService.getUsers().subscribe((users) => {
      this.users = users;
      setTimeout(() => this.scrollTargetIntoView());
   )};
 }

  scrollTargetIntoView() {
    if (document.getElementById('target')) {
        document.getElementById('target').scrollIntoView({
          behavior: 'auto',
          block: 'center',
          inline: 'center',
       });
   }
GreyBeardedGeek
  • 29,460
  • 2
  • 47
  • 67
  • I had to use this technique once. I needed to wait for DOM loaded to pass ID of "select" tag element inside Angular template to external JS-file to style it. – Sam Alekseev Apr 29 '22 at 23:32
5

a better solution is to use ViewChildren decorator because you can subscribe to changes of the view this mean you don't need to use setTimeOut

  @ViewChildren("target") target: QueryList<ElementRef>;

  ngOnInit() {
   this.dataService.getUsers().subscribe((users) => {this.users = users) ;  
  }

  ngAfterViewInit() {
    this.target.changes.subscribe(({ first: elm }) => {

      elm.nativeElement.scrollIntoView({
        behavior: "auto",
        block: "center",
        inline: "center"
      });

    });
  }

stackblitz demo

Muhammed Albarmavi
  • 23,240
  • 8
  • 66
  • 91
2

If your Angular app has routing, then another option you can look into is Route Resolvers, which can pre-fetch data for a component before navigating to a certain route. This way, your UI will have all the necessary data it needs to render in the ngOnInit() lifecycle hook.

You can create a resolver service using the following code:

@Injectable()
export class UserResolverService implements Resolve<Observable<User[]>> {
  constructor(private dataService: DataService) {}

  resolve() {
    return this.dataService.getUsers()
  }
}

And then in your route definitions, you can activate the resolver using:

const routes: Routes = [
  {
    path: 'my_path',
    component: MyComponent,
    resolve: {
      users: UserResolverService
    }
  }
];

And then finally, in your components ngOnInit() lifecycle hook, you can consume the data using the following code:

constructor(private route: ActivatedRoute) {}

ngOnInit() {
  this.users = this.route.snapshot.data.users;
}

ngAfterViewInit() {
  if (document.getElementById('target')) {
    document.getElementById('target').scrollIntoView({
      behavior: 'auto',
      block: 'center',
      inline: 'center',
    });
  }
}
Gabor Szekely
  • 1,108
  • 5
  • 14