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?
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();
}
}
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();
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With