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.