0

So I have this child component, where the id property is set randomly :

export class FileSelectionComponent implements AfterViewInit {
  public type = 'app-file-selection';
  public id = 'FileSelection#' + Math.random().toString(16).slice(2, 8);
  @Input() jspinst: any;
  public x;
  public y;
  public selected_file: string;

  constructor(public dialog: MatDialog, private httpClient: HttpClient) {
    this.selected_file = '';
    console.log('constructor called');
    console.log(this.id);
  }
  ngAfterViewInit() {
    ...
    this.jspinst.addEndpoint(this.id, { anchor: 'Right'}, Endpoint1);
    this.jspinst.addEndpoint(this.id, { anchor: 'Left'}, Endpoint2);
    this.jspinst.draggable(this.id);
  }
}

and the parent component goes like this:

export class FlowComponent implements OnInit, AfterViewInit, OnChanges {
 public nodes: Array<any>;
 public jspl;

  constructor() {
    this.nodes = [];
    this.jspl = jsPlumb.getInstance();
  }

  addNode(type) {
        let nn = new FileSelectionComponent();
        this.nodes = this.nodes.concat([nn]);
        s = this.nodes;
        console.log('object created and added');
  }

  ngAfterViewInit() {
    s = this.nodes;
    this.jspl.bind('connection', function (info) {
      console.log(info.sourceId+' ----> '+info.targetId); //this output
      console.log(s[0].id+' ----> '+s[1].id); // and this output are not the same while they should
      console.log(Object.keys(s[0]));
      console.log(Object.values(s[0]));
    });
  }
}

the addNode methode is called when I click a button, and as you can guess the constructor for the FileSelectionComponent is called twice, generating two different ids, which making it impossible for me retrieve the concerned nodes when the connection event is fired. I found some similar questions like this one, but none helped:

  1. My button have a type="button" on it
  2. I'm not bootstrapping both parent and child component, actually I'm not bootstrapping any of them, I already checked the app.module.ts (I don't even know bootstrapping is about).
  3. I didn't forget to close the selector tag in the host component.
  4. I don't have a platformBrowserDynamic().bootstrapModule(AppModule); in my code.
  5. the compiler doesn't show any error messages.

the Template for the parent goes like this:

<div id="cont">
  <div *ngFor="let n of nodes">
    <app-file-selection [jspinst]="jspl" *ngIf="n.type === 'app-file-selection'"></app-file-selection>
  </div>
</div>
  <button type="button" mat-icon-button matTooltip="Files selection" (click)="addNode('file-selection')"><mat-icon aria-label="Side nav toggle icon">insert_drive_file</mat-icon></button>

I know this kind of questions was asked over and over, but my searchs don't seem to be helpful, thanks in advance.

EDIT I already tried placing that random assignement inside the constructor (resulting in two ids one caught by jsplumb and one received by the parent component), and placing it inside the OnInit (results in just one id but not known by the parent component).

Muhammad
  • 13
  • 3

2 Answers2

0

You can use the local variables "let i = index" to get your index of *ngFor array ( in your case , nodes ) . And pass back using (click), write the logic in your parent.component.ts file.

<div *ngFor="let n of nodes; let i = index">
  <<app-file-selection (click)="addNode(i)></<app-file-selection>
</div>
Joe Lee
  • 49
  • 4
  • thanks for the quick response. I don't understand how can I use the index to stop the constructor from being called twice? – Muhammad Jul 24 '19 at 14:30
0

Your <app-file-selection></app-file-selection> tag is already instantiating the component by default. That means that it gets instanciated both in your template and in your logic. You don't need to use a New with angular components generally speaking.

You can go for something like this:

this.nodes = new BehaviorSubject<Array<any>>([]);
this.fileSelectorNodes = [];

ngOnInit() {
  this.nodes.subscribe(nodes => {
    this.updateNodes(nodes);
  });
}

addNode(type) {
  const nodes = this.nodes.getValue();
  nodes.push(type);
  this.nodes.next(nodes);
}

updateNodes(nodes: Array<any>) {
  this.fileSelectorNodes = nodes.filter(node => node === 'app-file-selection');
}
<app-file-selection [jspinst]="jspl" *ngFor="let fileSelector of fileSelectorNodes"></app-file-selection>
Alex
  • 1,090
  • 7
  • 21
  • thanks this did work and the constructor is called only once now, but I don't a list with objects now, it's just a list of strings, how can I access the child element properties like `id`, `selected_file`, `type`,... ?? – Muhammad Jul 24 '19 at 14:51
  • If you need to access some data from your child component, use Angular bindings like Output: https://angular.io/guide/component-interaction – Alex Jul 24 '19 at 15:02
  • Actually I was trying to avoid that, I'm planing to use different kinds of nodes not just `FileSelectionComponent` and passing data from one to the other. Using `@Output` and `@Input` will make it happen but it would be more complex than just objects in a list, where I can directly acces their attributes. If there is no work around then I'll take that. – Muhammad Jul 24 '19 at 16:04
  • Your way would work too, but it's getting out of the framework kinda. I would create a service to store the common data for all involved components to access it instead, containing both the array of nodes and the data generated by the child components. You can use an Enum for your node types and use an ngSwitch to decide which to display depending on the id – Alex Jul 24 '19 at 16:09
  • Great, that service approach might be exactly what I need. Thanks – Muhammad Jul 24 '19 at 16:39