Search code examples
angularobservablesubscription

Subscribing to an Observable based on conditions


Background

Im using Reactive Forms that expands over two tabs (first half of the page alone has tabs) and then a long page with Submit button at the bottom. I do the validation on click of the Submit button.

On clicking of Submit button, the page needs to scroll to the error form field when Validation fails.

I'm also able to load the tab based on errors in FormGroups.

Problem

  1. The scroll happens before the tab loads.
  2. To solve Point 1, I subscribed to the animationDone event and then scrolled to the point inside of it.
submit(){
   //code to select the desired tab
   this.selectedTab.animationDone.subscribe(res => {
   this.myElement.nativeElement.ownerDocument.getElementsByClassName('ng-invalid mat-form-field')[0].scrollIntoView({ behavior: 'smooth' });
          });
}

All works fine until this point !!!

  1. Once the Approve Button is clicked, the subscription subscribes, and the page scrolls to the errors whenever a new Tab is selected. I wanted to unsubscribe to this observable and later subscribe back whenever the Submit button is clicked again.

  2. I tried unsubscribe, but I'm not able to subscribe to it again. This sometimes destroys the subscribe function and throws error

I think I'm missing something and reaching out for help. Thanks in advance !!

More Code as Requested

submit()
{
    if(this.bookingForm.valid){
    // do some actions ....
     }
    else {
    this.bookingForm.markAllAsTouched();
    //subscribing to the event before selecting the tab

    this.selectedTab.animationDone.subscribe(res => {

     this.myElement.nativeElement.ownerDocument.getElementsByClassName('ng-invalid mat-form-field')[0].scrollIntoView({ behavior: 'smooth' });

          });

    // code to select the Tab where the error occurs .....
     this.selectedTab.selectedIndex = this.errorIndex;

    //unsubscribe
    this.selectedTab.animationDone.unsubscribe()

    } // close of else block
}// close of submit Function

The unsubscribe (or similar function) is required as this should subscribe only when the Submit button is clicked. If it is not unsubscribed (or paused) then the subscribe function is being called everytime the tab is changed and the page keeps scrolling up and down based on errors.

Page View

This is just for demonstration. Page view

EDIT 2 below

Here is the StackBlitz Link. This is just a sample page, My page has much more fields and form Groups compared to this.

Recreating the Issue

Scenario 1

  1. Fill in the fields - Name , Tab1 , Details1
  2. Keep tab1 selected and click on Submit.
  3. This should scroll up and show Tab2. Works as expected! The delay is agreeable.
  4. Now manually switch to Tab1.
  5. You can see the page scrolling to the Details 2 field.
  6. This scroll happens whenever the tab is switched. How do i stop this?
  7. Im able to achieve this by uncommenting line 38 in app.component.ts file. - The unsubscribe command.
    • In this, there is an error when i click on the Submit button more than once.
    • When i click for the second time, the subscribe dosen't work.

Solution

  • Sorry for the late response. I was a little busy.

    I didn't look into the animation part that you were trying, instead I focused on the basics as we just needed to focus on the element if it is invalid.

    Method 1

    submit() {
      if(this.testForm.valid) {
        //Code to Submit
      } else {
        ((document.getElementsByClassName('mat-input-element ng-invalid')[0]) as HTMLElement).focus();
      }
    }
    

    Method 2

    I came across this very cool way to do this.

    import { MatInput } from '@angular/material';
    import { QueryList, ViewChildren } from '@angular/core';
    
    @ViewChildren(MatInput) inputs: QueryList <MatInput>;
    
    submit() {
      if(this.testForm.valid) {
        //Code to Submit
      } else {
        this.inputs.find(input => !input.ngControl.valid).focus();
      }
    }
    

    Also, if you always want Tab 1 to be selected first if it is invalid, then put the other condition in else/ else if section -

    if(this.testForm.get('tab1').errors) {
      this.tabControl.selectedIndex = 0;
    } else if(this.testForm.get('tab2').errors) {
      this.tabControl.selectedIndex = 1;
    }
    

    One more thing, you'll have to put a slight delay before focusing on invalid elements as creating input element for Tab 2 in DOM takes nanoSeconds of time and it focuses on Details 1 if either of the Tab 1 or 2 is invalid.

    delay(ms: number) {
        return new Promise( resolve => setTimeout(resolve, ms) );
      }
    

    Just call this before focus() and after switching tabs check

    await this.delay(1);
    

    If you don't want to set delay, just set focus() on animationDone event again and set the animationDuration time to minimum so you wouldn't see focus on Details 1 in real time.

    Follow the Methods links for complete flow and let me know if you face any issues.