Search code examples
angulartypescriptdesign-patterns

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!


Solution

  • As suggested in my comment (simple suggestion):

    // kid.component.ts
    import { Component } from '@angular/core';
    
    @Component({
        selector: 'app-kid',
        templateUrl: './kid.component.html',
    })
    export class KidComponent {
        @Input() set width(w: number) {
            if(this._width === w) return;
            this._width = w;
            this.draw();
        }
    
        @Output() widthChange = new EventEmitter<number>();
    
        private _width = 100;
    
        onUserResize(newWidth: number) {
            this.width = newWith;
            this.widthChange.emit(newWidth);
        }
    
        draw() {
            // Drawing code
        }
    }
    

    As Brandon Tylor mentioned you could also use services for communication between siblings but i don't know if it is not going to get complicated because you just need one property.