mercredi 8 mars 2023

Angular - Proper way to share reaction between sibling components

The title is not very explanatory, but I couldn't find any better way to phrase my problem.

I'm working with angular, and I've been facing a problem a few times now. Let me show it as an example :

Let's say I have a mother component MomComponent, which contains multiple KidComponent.

Each KidComponent can be resized by the user. If that happens, all of the other kids should be resized as well.

A Kid component should also be able to live without siblings.

Here's my code :

// mom.component.ts
import { Component } from '@angular/core';

@Component({
    selector: 'app-mom',
    templateUrl: './mom.component.html',
})
export class MomComponent {
    width: number;
}
<!-- mom.component.html -->

<app-kid [(width)]="width"></app-kid>

<app-kid [(width)]="width"></app-kid>

<app-kid [(width)]="width"></app-kid>
// kid.component.ts
import { Component } from '@angular/core';

@Component({
    selector: 'app-kid',
    templateUrl: './kid.component.html',
})
export class KidComponent {
    @Input() set width(w: number) {
        this._width = w;
        this.draw();
    }
    @Output() widthChange = new EventEmitter<number>();

    private _width = 100;

    onUserResize(newWidth: number) {
        this.widthChange.emit(newWidth);

        this._width = newWidth;
        this.draw();
    }

    draw() {
        // Drawing code
    }
}

My problem here, is that the kid which has been resized will be drawn 2 times. One first time because I call it internally, and a second time because the mom's variable width will be updated.

I could change my kid component as follows, so that it's only redrawn from external change :

// kid.component.ts
export class KidComponent {
    // [...]

    onUserResize(newWidth: number) {
        this.widthChange.emit(newWidth);

        // I remove these 2 lines:
        //
        // this._width = newWidth;
        // this.draw();
    }
  
    // [...]
}

But then, I also want to be able to use my KidComponent on its own without sharing its width with other kids (just <app-kid></app-kid>), in which case, the above code wouldn't work.


The only solution that I can think of right now is the following :

// kid.component.ts
export class KidComponent {
    // [...]

    onUserResize(newWidth: number) {
        this.widthChange.emit(newWidth);

        if (!this.widthChange.observers.length) {
            // No-one is listening to the event, I redraw myself
            this._width = newWidth;
            this.draw();
        } else {
            // Someone is listening to my resize, let's hope they
            // take care of redrawing me...
        }
    }

    // [...]
}

But I don't really like this approach, the fact that there is an observer on the widthChage emitter doesn't mean that this observer will redraw the KidComponent.

So, my question is : Is there a better approach that I'm missing?

Thanks!

Aucun commentaire:

Enregistrer un commentaire