0

I want to pass data from a grandparent to a grandchild. Using @Input seems cumbersome because I'd have to use it twice - I also believe it would make refactoring harder, if for example these components became siblings.

In other words, I want to avoid using @Input twice, one in the child, one in the grandchildren, for passing the same piece of data. I currently prefer using shared services to share data.

Here is the HTML:

// grandparent

<div *ngIf="daPanel.expanded === true">
 <div *ngFor="let e of elements;">
  <app-history-item-tab-group></app-history-item-tab-group>
 </div>
</div>

// child

<div>
  <mat-tab-group>
    <mat-tab label="Settings">
      <app-history-settings></app-history-settings>
    </mat-tab>
    <mat-tab label="Events List">
      <app-history-events-list></app-history-events-list>
    </mat-tab>
    <mat-tab label="Generated Code">
      <app-history-generated-code></app-history-generated-code>
    </mat-tab>
  </mat-tab-group>
</div>

// grandchild

<div class="language-js">

  <mat-card>

    <button id="copy-code-btn" style="float:right;" 
            mat-raised-button (click)="copyCodeToClipboard()">Copy Code to Clipboard</button>

    <button style="float:right;" mat-raised-button>Save Code to File</button>

    <mat-card-title>Here is your generated code</mat-card-title>
    <mat-card-subtitle>JavaScript</mat-card-subtitle>


    <mat-card-content [style.overflow]="'auto'" [style.height.px]="'500'">
      <pre [innerHTML]="highlightedCode"></pre>
    </mat-card-content>

    <mat-card-actions>
      <button mat-button>LIKE</button>
      <button mat-button>SHARE</button>
    </mat-card-actions>
    <mat-card-footer></mat-card-footer>
  </mat-card>

</div>

As opposed to using @Input to pass data, I'd rather share a unique instance of a service between the components, but I don't know how to do that.

To be exact - ideally I could create a unique service instance for the grandparent, and share a reference to that unique service in the grandparent, the child, and the grandchild...but how?

Here is the grandparent component:

@Component({
  moduleId: module.id,
  templateUrl: './history.component.html',
  styleUrls: ['./history.component.scss'],
  encapsulation: ViewEncapsulation.None,
  preserveWhitespaces: false,
})

export class HistoryComponent implements OnInit {
  displayMode: string = 'default';
  multi = false;
  hideToggle = false;
  disabled = false;
  showPanel3 = true;
  expandedHeight: string;
  collapsedHeight: string;
  elements : Array<any>;

  constructor(private mds: MainDataService) {

    const v = this.mds.key;
    this.elements = JSON.parse(localStorage.getItem(v) || '[]');

  }

  ngOnInit() {
  }

}

Here is the child component:

@Component({
  selector: 'app-history-item-tab-group',
  templateUrl: './tab-group-history.component.html',
  styleUrls: ['./tab-group-history.component.scss']
})


export class TabGroupHistoryComponent implements OnInit {

  constructor() {
  }

  ngOnInit() {
  }

}

Here is the grandchild component:

@Component({
  selector: 'app-history-generated-code',
  templateUrl: './history-generated-code.component.html',
  styleUrls: ['./history-generated-code.component.scss'],
  styles: [`
  mat-card { margin:2em; }
  `]
})


@Inject(ChromeDataService)
@Inject(MainDataService)

export class HistoryGeneratedCodeComponent implements OnInit {

  highlightedCode: string;
  googleCodo: string;
  rawCode = '';
  mySub: Subscription;
  codeTreeRoot = values.top.copy();
  currentNode = this.codeTreeRoot;
  rawGeneratedCode = '';
  formattedCode: string;


  constructor(private mds: MainDataService) {

    this.formattedCode = ' // (no code generated yet)';
    this.highlightedCode = Prism.highlight(this.formattedCode, Prism.languages.js);

  }

  ngOnInit() {


  }

  ngOnDestroy() {
    this.mySub.unsubscribe();
  }

  ngAfterContentInit() {
    Prism.highlightAll(true, function () {
      console.log('highlighted...');
    });
  }

  copyCodeToClipboard() {
    const textarea = document.createElement('textarea');
    textarea.setAttribute('type', 'hidden');
    textarea.textContent = this.formattedCode || this.rawCode;
    document.body.appendChild(textarea);
    // textarea.focus();
    textarea.select();
    document.execCommand('copy');
    textarea.parentNode.removeChild(textarea);
  }

  updateCode() {
    this.rawGeneratedCode = this.codeTreeRoot.generate();
    this.formattedCode = js_beautify(this.rawGeneratedCode, {
      brace_style: 'preserve-inline',
      max_preserve_newlines: 2
    });

    this.highlightedCode = Prism.highlight(this.formattedCode, Prism.languages.js);
  }


}
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • The only idea I have to is create a service of services, using a hashmap. But the problem with that, is that I still don't have the lookup data in the grandchild to look up the right service instance in the hashmap. – Alexander Mills Feb 01 '18 at 05:34
  • Isn't it Dependency Injection? register your service in NgModule and then inject them in constructors like `constructor(myService:MyService){}` – Haifeng Zhang Feb 01 '18 at 05:35
  • I have multiple instances of the service - a unique service instance is created for each element in an array. There is currently no way for the grandchild to know which instance of the service to reference. – Alexander Mills Feb 01 '18 at 05:37
  • can you show your TS code other than just the template? – Haifeng Zhang Feb 01 '18 at 05:38
  • I made it clearer, by adding an ngFor call in the grandparent html – Alexander Mills Feb 01 '18 at 05:39
  • sure, I can share the TS code - note that this is the same problem of relating cousins with cousins - how can cousins share the same data as their grandparent. – Alexander Mills Feb 01 '18 at 05:39
  • I added the TS code, I just want to share data between the grandparent and grandchild – Alexander Mills Feb 01 '18 at 05:43
  • This question might be better presented as sharing data between two 'unrelated' siblings, because it's basically the same problem. – Alexander Mills Feb 01 '18 at 05:46
  • I don't think your code work. How can you add `@Inject(MainDataService)` for your component? the keyword is Injectable and the syntax is wrong as well. If you want to use the same service instance, just register it in your NgModule and all components under the module would share same instance – Haifeng Zhang Feb 01 '18 at 05:51
  • I am going to have to delete the question and present it in a different way, thanks though – Alexander Mills Feb 01 '18 at 05:51
  • @haifzhan this is a similar question, asked in a different way: https://stackoverflow.com/questions/48556369/share-data-between-sibling-components-using-non-singleton-service – Alexander Mills Feb 01 '18 at 06:28

1 Answers1

0

Basically I want a grandparent to communicate with its grandchildren, and I want some of the grandchildren to be able to communicate with each other.

The best way I found to do this, was to pass a uuid to the grandchildren, using two @Input calls. An @Input from grandparent to child, then @Input from child to grandchild.

Then in the grandchildren, I used that uuid to look up a unique instance of a certain class. The unique instance of the class was stored in a hashmap in the singleton service.

Not sure if there is an easier way to do this, although I guess this is not that bad.

For example, here is the singleton service:

// a helper class

export class Foo {

}

// the shared singleton service

@Injectable()
export class SharedSingletonService {

  foos : <{[key:string]:Foo}> = {}

  constructor(){

  }

  getInstanceOfFoo(uuid){
    if(this.foos[uuid]){
       return this.foos[uuid];
     }

    return this.foos[uuid] = new Foo();
  }

}
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817