angularrouter-outletangular-router-guards

How to pass data from guard of a child component to parent component?


I have a parent component that has a bun of tabs with links. Each tab has a link that will navigate to the child component. The parent tab has a header data such as client name, dob, gender, ...

The parent component .HTML looks like the following:

 <mat-card>
    <mat-card-header>
      <mat-card-title>
        <mat-chip-list aria-label="Title" class="table_title">
          <mat-chip color="primary" selected>Client Detail</mat-chip>
          <mat-chip color="accent" *ngIf="clientName">Client Name: {{ clientName }}</mat-chip>
          <mat-chip color="accent" *ngIf="clientAlias">DOB: {{ clientDOB }}</mat-chip>
          <mat-chip color="accent" *ngIf="clientAlias">Gender: {{ clientGender }}</mat-chip>

        </mat-chip-list>
      </mat-card-title>
    </mat-card-header>
    <mat-card-content>
      <nav mat-tab-nav-bar>
        <a mat-tab-link
          [routerLink]="['./demographics']"
          (click)="activeLink = 'demographics'"
          [active]="activeLink == 'demographics'">
          <mat-icon class="tab-icon">contact_page</mat-icon>
          Identifying Information
        </a>
        <a mat-tab-link
          [routerLink]="['./contacts']"
          (click)="activeLink = 'contacts'"
          [active]="activeLink == 'contacts'">
          <mat-icon class="tab-icon">contacts</mat-icon>
          Contacts
        </a>
      </nav>

      <router-outlet></router-outlet>
    </mat-card-content>
  </mat-card>

The router outlet will render the appropriate child component based on the tab name.

Parent component .ts looks like the following:

ngOnInit() {
    // Calling the resolver and updating the activeLink
    this.activeLink = this.route.snapshot.data.tab;

    // Set the header data
    this.route.data.subscribe(resolvedData => {
      if (resolvedData.headerData !== null) {
        this.clientName = resolvedData.headerData.fullName;
        this.clientDOB = resolvedData.headerData.dob;
        this.clientGender = resolvedData.headerData.gender;
      } else {
        this.router.navigate(['/clients']);
      }
    });
  }

Navigating (rendering) the child component (demographics) will let the user edit the client info such as name, gender, dob.

The save of the client demographics page (component) is done using a guard (Deactivate) which means the user can't navigate to the contact page or other page (tab) unless data is valid.

My deactivate guard looks like the following:

if (model.dirty && model.valid) {
      // call the saving service
      this.clientService.UpdateClientIdentInfo(personId, model.getRawValue()).subscribe(updatedHeaderData => {
        // TODO: PASS updatedHeaderData TO THE PARENT COMPOENET
        this.snackBar.open('Data has been saved successfully!', '', {
          duration: 2000,
          horizontalPosition: 'center',
          verticalPosition: 'bottom',
          panelClass: ['snack-success']
        });
        return true;
      }, error => {
        return false;
      })
    }

The response of this API call updatedHeaderData will have the new data that I want to pass to the parent component so all of its header data gets updated.

Here is my app routing model

{
    path: 'clients/:id',
    component: ClientDetailsComponent,
    resolve: {
      tab: ClientDetailsResolver,
      headerData: ClientDetailHeaderDataResolver
    },
    children: [
      {
        path: 'demographics',
        component: ClientIdentInfoComponent,
        resolve: { client: ClientIdentInfoResolver },
        canDeactivate: [ ClientIdentInfoGuard ]
      },
      {
        path: 'contacts',
        component: ClientContactComponent,
        resolve: { PersonContacts: ClientContactsResolver},
        canDeactivate: [ ClientContactsGuard ]
      },
      {
        path: '',
        redirectTo: 'demographics',
        pathMatch: 'full'
      }
    ]
  },

Solution

  • Use a Service here. Something like this.

    HeaderDataService

    private pHeaderData: Subject<any> = new Subject<any>();
    
    get headerData(): Observable<any> | any {
        return this.pHeaderData.asObservable();
    }
    
    set headerData(data: any) {
        this.pHeaderData.next(data);
    }
    

    ParentComponent

    const subscription: Subscription = new Subscription();
    
    constructor(private headerDataService: HeaderDataService) { }
    
    ngOnInit(): void {
        this.subscription.add(
            this.headerDataService.headerData.subscribe( data => {
                console.log('delivery from ChildComponent arrived: ', data);
            })
        );
    }
    
    ngOnDestroy(): void {
        this.subscription.unsubscribe();
    }
    

    ChildComponent

    constructor(private headerDataService: HeaderDataService) { }
    
    if (model.dirty && model.valid) {
      // call the saving service
      this.clientService.UpdateClientIdentInfo(personId, model.getRawValue()).subscribe(updatedHeaderData => {
    
        // inform the parent
        this.headerDataService.headerData = updatedHeaderData;
    
        this.snackBar.open('Data has been saved successfully!', '', {
          duration: 2000,
          horizontalPosition: 'center',
          verticalPosition: 'bottom',
          panelClass: ['snack-success']
        });
        return true;
      }, error => {
        return false;
      })
    }