可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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;
}