Angular's Getting Started Guide contains recipes for common component communication scenarios in which two or more components share information.
The scenarios discussed, include:
- Pass data from parent to child with input binding
- Intercept input property changes with a setter
- Intercept input property changes with ngOnChanges()
- Parent listens for child event
- Parent interacts with child via local variable
- Parent calls an @ViewChild()
- Parent and children communicate via a service
Each scenario discusses parent -> child or child -> parent interaction.
In this post, I'll walk you through the steps I followed to enable sibling component interaction.
Excel-like UI
In the previous post, I wrote about the steps I followed when creating an application with an Excel-like UI.
The following regions compose Excel's UI:
- Quick Access Toolbar
- Ribbon (including Ribbon Tabs)
- Formula Bar
- Worksheet Grid
- Worksheet Tabs
- Status Bar
At this point, the application has a Ribbon component, a Formula Bar and a Worksheet component:

The Application Component
Let's take a look at the template for the AppComponent (app.component.html):
<div class="excelbook" style="position: relative; overflow-y: hidden;" (window:resize)="onResize($event)">
  <div id="ribbon-tabs-container">
    <app-ribbon (ribbonClicked)="ribbonClicked($event)"></app-ribbon>
  </div>
  <div id="worksheet-container" [style.height.px]="worksheetHeight">
    <app-worksheet></app-worksheet>
  </div>
</div>
The AppComponent (the parent) has two child components: the RibbonComponent (<app-ribbon>); and the WorksheetComponent (<app-worksheet>). The RibbonComponent and the WorksheetComponent are siblings:
   
The Ribbon Component
Let's take a look at the template for the RibbonComponent (ribbon.component.html):
<div class="row ribbon-container">
  <!-- File Panel -->
  <div class="panel panel-default">
    <div class="panel-body">
      <div class="btn-group">
        ...
        <div class="btn btn-default btn-large no-border">
          <span class="glyphicon glyphicon-open"></span>
          <span class="text">Load</span>
          <input type="file" class="load"
          (change)="command({ methodName: 'fileLoad', param1: $event })"
          accept="application/vnd.openxmlformats-
            officedocument.spreadsheetml.sheet, 
          application/vnd.ms-excel.sheet.macroEnabled.12" />
        </div>
      </div>
    </div>
    <div class="panel-footer text-center">File</div>
  </div>
  
  ...
  
</div>
When a user click's the 'Load' button the RibbonComponent's command() method is invoked:
export class RibbonComponent {
  @Output() ribbonClicked = new EventEmitter<any>();
  command(action: any) {
    this.ribbonClicked.emit(action);
  }
}
And the RibbonComponent emits a ribbonClicked event that the parent (the AppComponent) binds to:
  <div id="ribbon-tabs-container">
    <app-ribbon (ribbonClicked)="ribbonClicked($event)"></app-ribbon>
  </div>
The parent (the AppComponent) inject's a child component (the WorksheetComponent) as a @ViewChild so that it can react to the ribbonClicked event:
export class AppComponent implements OnInit {
  @ViewChild(WorksheetComponent)
  private worksheet: WorksheetComponent;
  
  ...
  
  ribbonClicked(event: any) {
    const methodName = event.methodName;
    if (this.worksheet[methodName]) {
      if (event.hasOwnProperty('param1')) {
        const param1 = event.param1;
        this.worksheet[methodName](param1);
      } else {
        this.worksheet[methodName]();
      }
    }
  }
}  
By invoking a WorksheetComponent method, for example, fileLoad():
  fileLoad(event: any) {
    if (this.flexSheet && event.target.files[0]) {
      this.flexSheet.loadAsync(event.target.files[0]);
      event.target.value = '';
    }
  }
You should see output like:

In the scenario discussed above, we have enabled sibling component interaction (albeit indirectly) by utilising the Parent listens for child event and the Parent calls an @ViewChild() component communication scenarios.
Service, Observable and a Subject
Another approach to sibling component interaction is to use a Service, an Observable and a Subject.
The Worksheet Service
Let's take a look at the Worksheet Service (worksheet.service.ts):
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class WorksheetService {
  private selectionFormatState = new Subject<any>();
  setState(state: any) {
    this.selectionFormatState.next(state);
  }
  getState(): Observable<any> {
    return this.selectionFormatState.asObservable();
  }
}
The Worksheet Component
The WorksheetComponent can use the WorksheetService's setState() method to share state information:
import * as wjcGridSheet from 'wijmo/wijmo.grid.sheet';
import { WorksheetService } from '../services/worksheet.service';
export class WorksheetComponent {
  @ViewChild('flexSheet')
  private flexSheet: wjcGridSheet.FlexSheet;
  
  selectionFormatState: wjcGridSheet.IFormatState = {};
  constructor(private worksheetService: WorksheetService) {
  }
  
  applyBoldStyle() {
    if (this.flexSheet) {
      this.flexSheet.applyCellsStyle({ fontWeight: this.selectionFormatState.isBold ? 'none' : 'bold' });
      this.selectionFormatState.isBold = !this.selectionFormatState.isBold;
      this.worksheetService.setState(this.selectionFormatState);
    }
  }
}
The Ribbon Component
The RibbonComponent can use the WorksheetService's getState() method to obtain (WorksheetComponent) state information:
import { WorksheetService } from '../services/worksheet.service';
import { Subscription } from 'rxjs/Subscription';
export class RibbonComponent implements OnDestroy {
  private subscription: Subscription;
  selectionFormatState: any = {};
  constructor(private worksheetService: WorksheetService) {
    this.subscription = this.worksheetService.getState().subscribe(
      selectionFormatState => {
        this.selectionFormatState = selectionFormatState;
      });
  }
  
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}
Now we can use the selectionFormatState to toggle the Ribbon  buttons active state:
  <!-- Font Panel -->
  <div class="panel panel-default">
    <div class="panel-body">
      <div class="btn-group-vertical">
        <div class="btn-group btn-group-h">
          <button type="button" class="btn btn-default btn-small {{selectionFormatState.isBold ? 'active' : ''}}"
                  title="Bold" (click)="command({ methodName: 'applyBoldStyle' })">
            <span class="glyphicon glyphicon-bold"></span>
            <span class="text">Bold</span>
          </button>
          
          ...
          
        </div>
      </div>
    </div>
    <div class="panel-footer text-center">Font</div>
  </div>          
The Ribbon's buttons reflect the WorksheetComponent's selectionFormatState:

Source Code:
- GitHub: angular-wijmo-flexsheet
References:
- Angular Docs: Component Interaction
Additional Resources:
- Compodoc: Getting Started Guide
- wijmo 5 Forum: FlexGrid Import/Export to Excel using JSZip in Webpack
