Angular2 scroll to element that has *ngIf

2020-03-01 06:10发布

问题:

I am writing a form in Angular 2 where the user submits the form, it is validated and if there are any errors with the inputs, I want to scroll the user's browser to the first element with the class "error"

The problem is, all of my errors use *ngIf like so:

<input type="text" [(ngModel)]="model.first_name">
<div class="error" *ngIf="errors.first_name">
    {{errors.first_name}}
</div>

In my submit function

submit(){
   this.errors = this.validate();
   if(this.errors.any()){
      var errorDivs = document.getElementsByClassName("error");
      if(errorDivs.length > 0){
         errorDivs[0].scrollIntoView();
      }
   }
}

I realize this is because *ngIf removes the div from the DOM completely and the Angular check for changes hasn't been given a chance to run yet. Is there a clever and clean way to do this?

回答1:

Not sure I fully understand your question.

Using a directive like below would make the element scroll into view when errors.first_name becomes truthy:

<div class="error" *ngIf="errors.first_name" scrollTo>
    {{errors.first_name}}
</div>
@Directive({ selector: '[scrollTo]'})
class ScrollToDirective implements AfterViewInit {
  constructor(private elRef:ElementRef) {}
  ngAfterViewInit() {
    this.elRef.nativeElement.scrollIntoView();
  }
}


回答2:

Here's a way I figured out:

Create a helper class

export class ScrollHelper {
    private classToScrollTo: string = null;

    scrollToFirst(className: string) {
        this.classToScrollTo = className;
    }

    doScroll() {
        if (!this.classToScrollTo) {
            return;
        }
        try {
            var elements = document.getElementsByClassName(this.classToScrollTo);
            if (elements.length == 0) {
                return;
            }
            elements[0].scrollIntoView();
        }
        finally{
            this.classToScrollTo = null;
        }
    }
}

Then create one in the component you wish to use it in

private scrollHelper : ScrollHelper = new ScrollHelper();

then when you find out you have errors

submit(){
   this.errors = this.validate();
   if(this.errors.any()){
        this.scrollHelper.scrollToFirst("error");
   }
}

then postpone the actual scroll until ngAfterViewChecked after *ngIf has been evaluated

ngAfterViewChecked(){
    this.scrollHelper.doScroll();
}


回答3:

This may not be the most elegant solution, but you can try wrapping your error focusing code in a setTimeout

submit(){
   this.erroors = this.validate();
   setTimeout(() => {
     if(this.errors.any()){
        var errorDivs = document.getElementsByClassName("error");
        if(errorDivs.length > 0){
          errorDivs[0].scrollIntoView();
        }
     }
   }, 50);
}

The key isn't so much to delay the focusing code as much as it is about ensuring that the code runs after a tick of the event loop. That will give the change detection time to run, thus ensuring your error divs have time to be added back to the DOM by Angular.



回答4:

I've created a package that handles scrolling to element in Angular 4+. Everything works for AoT and server-side rendering, in case you're using Angular Universal.

NPM
@nicky-lenaers/ngx-scroll-to

GitHub
@nicky-lenaers/ngx-scroll-to

Also, I've recently created a Plunker that shows scrolling to elements that are initially hidden by *ngIf, which solves your problem.

Check out the Plunker for a working ngIf example



回答5:

My solution, which is quite simple.

Steps

create a member:

scrollAnchor: string;

create a method that will scroll to anchor if the anchor is set.

ngAfterViewChecked(){
  if (this.scrollAnchor) {
    this.scrollToAnchor(this.scrollAnchor);
    this.scrollAnchor = null;
  }
}

and then when you want to scroll to somewhere that is in a div that has a ngIf, simply set the scrollAnchor

this.scrollAnchor = "results";

Very simple! Note you have to write the scroll to anchor method yourself... or scroll to element or whatever. Here is mine

 private scrollToAnchor(anchor: string): boolean {
    const element = document.querySelector("#" + anchor);
    if (element) {
      element.scrollIntoView({block: "start", behavior: "smooth"});
      return true;
    }
    return false;
  }


标签: angular