NachoDawg
NachoDawg

Reputation: 1544

Angular 6 - Using Observable to trigger event between siblings

So I have a collection of divs in app-component filled with my app-cube components, and I have an array filled with GridProp objects in the parent component which's indexes represents each cell in the collection, meaning I create the content of each "cell" based on the content of array[i].

At array[i] I have an object with an Observable in it among other things like the content for cells. When I update something in cell i I want to update the siblings of cell [i-1] and [i+1].

I am trying to do this by having each app-cube subscribe to the observable in the matching object in the array. This way I should be able to simply send a value to the observer in the array at any index and have it trigger the subscribed components subscription handler, right?

The objects in the array are of this class

export class GridProp implements OnInit {
  private terrain = new Terrain();
  coord: Array<number>;
  cont: {};

  observer: Observer<string>;
  observable: Observable<string> = new Observable((observer: Observer<string>) => {
    this.observer = observer;
  });
}

Then in the cube-component.ts

@Component({
  selector: 'app-cube',
  templateUrl: './cube.component.html',
  styleUrls: ['./cube.component.css']
})
export class CubeComponent implements OnInit {
  @Input() point; // Represents the relevant GridProp object

  ngOnInit() {
    this.point.observable.subscribe(this.handleCubeUpdate);
  }

  handleCubeUpdate() {
    console.log('handling!');
    this.updateWalls();
  }

  updateWalls() {
    // Do stuff to the "walls" values used in this cube.component.html
  }
  toggleWalls() {
    for (const direction in this.point.cont.terrain.walls) {
      if (this.getSide(direction) && this.getSide(direction).cont.terrain.isBlock) {
        this.point.cont.terrain.walls[direction] = false;
        this.getSide(direction).observer.next(true);
      } else {
        this.point.cont.terrain.walls[direction] = true;
      }
    }
    this.updateWalls();
  }
}

In the parent component I distrubte the app-cube

<div class="grid-point-z" *ngFor="let z of y">
  <div 
    class="grid-point"
    attr.data-point="{{ z.coord }}" 
    [ngStyle]="{ 
      'z-index': z.coord[2]
      }">
    <app-cube  
      [point]="z"
      [coord]="z"
      [level]="level"
      (click)="clickPoint(z)"
      (change)="onCubeChange($event)">
      {{z}}
    </app-cube>
  </div>
</div>

It crashes with the error

ERROR TypeError: this.updateWalls is not a function
    at SafeSubscriber.push../src/app/assets/cube/cube.component.ts.CubeComponent.handleCubeUpdate [as _next] (cube.component.ts:71)

So I guess it doesn't recognize this.handleCubeUpdate in the subscription in CubeComponent. Is there a work-around for this?

Upvotes: 0

Views: 1706

Answers (3)

Yury Polubinsky
Yury Polubinsky

Reputation: 373

From my point of view here we have kind of design problem.

In Angular, components can interact with each other using their @Input() and @Output() properties(Input - to be aware about changes from parent component and update view, Output - to notify others about changes inside).

So better to add to your CubeComponent output event somethingChanged and emit this event when, as you said, you update something.

export class CubeComponent implements OnInit {
  @Input() point; // Represents the relevant GridProp object
  @Output() somethingChanged = new EventEmitter<GridProp>();

  ngOnInit() {
  }

  notifyOthersComponentAboutUpdate() {
    let id = this.point; // You can emit point or some id, to identify it later in parent component
    this.somethingChanged.emit(id);
  }
}

In you parent component(app-component) which contains array of app-cube components create a method updateSiblings(idOfComponentWhichWasUpdated) and call it from app-cube component

<div class="grid-point-z" *ngFor="let z of y">
  <div 
    class="grid-point"
    attr.data-point="{{ z.coord }}" 
    [ngStyle]="{ 
      'z-index': z.coord[2]
      }">
    <app-cube  
      [point]="z"
      [coord]="z"
      [level]="level"
      (somethingChanged)="updateSiblings($event)">
      {{z}}
    </app-cube>
  </div>
</div>

And in that updateSiblings method your have access to your y array(*ngFor="let z of y") which contains all data for app-cube components(y: GridProp[]).
You have id of component which was updated, so you can find it in array y and you can find his siblings and update their data as well. Angular will automatically detect changes in that y array and your siblings will be updated on UI level.

Upvotes: 1

SebastianG
SebastianG

Reputation: 9584

Have you considered using a REDUX architecture in Angular?

Here's a good tutorial on how it works and the benefits. I have used the exact same tutorial to learn about it and implement.

You could then keep that array in your store and dispatch actions to change thing. Each action could contain the logic to update itself + the two items surrounding it.

Upvotes: 0

dK-
dK-

Reputation: 602

Now it make more sense to me that I could try to answer it here. Hopefully I could articulate this properly :).

So, when you pass in a function reference in your subscribe, this in handleCubeUpdate has a different context. You can change your function implementation to arrow function.

handleCubeUpdate = () => {
  this.updateWalls();
}

additionally, if your updateWalls is calling another function, then you should change that updateWalls function arrow function.

Upvotes: 2

Related Questions