Search code examples
javascripthtmlangulartypescriptangular-signals

I want to share a signal between components aross my application, alternatives to service implementation


My scenario is, I have a dialog, which is opened through a signal, but I want to trigger this dialog to open, across my application. The obvious way is to create a DialogService (providedIn: 'root') and store the signal there, but is there a more lightweight way to achieve the same.

Dialog component HTML:

@if(showDialog()) {
  <kendo-dialog 
    title="sometitle"
  >
    some text
  </kendo-dialog>
}

Dialog Component TS:

@Component({ ... })
export class DialogComponent {
  showDialog = signal(false); // <-- I want to share this signal, across multiple components.
}

Version is Angular 19 but am ok with something that is backwards compatible also.


Solution

  • For this solution you can look at the source code of angular.dev, they have a search popup (for searching documentation), this dialog component is opened/closed, from a signal, that is actually provided using an InjectionToken (providedIn: 'root' -> provided throughout the application), this token, thanks to Dependency injection can be shared across components easily and the dialog can be opened from anywhere.

    app.component.ts - Adev folder angular Github Source Code Reference

    Below is how we define a injection token signal:

    export const DIALOG_OPEN = new InjectionToken('DIALOG_OPEN', {
      providedIn: 'root',
      factory: () => signal(false),
    });
    

    Then we define the DialogCustomComponent to open based on this DI token.

    @Component({
      selector: 'app-dialog',
      imports: [DialogComponent, DialogActionsComponent],
      template: `
      @if(opened()) {
      <kendo-dialog title="Oh no!" (close)="close()">
        <p style="margin: 30px; text-align: center;">Dialog was opened.</p>
    
        <kendo-dialog-actions>
          <button kendoButton (click)="close()" themeColor="primary">
            Close
          </button>
        </kendo-dialog-actions>
      </kendo-dialog>
      }
      `,
    })
    export class DialogCustomComponent {
      opened: WritableSignal<boolean> = inject(DIALOG_OPEN);
      close() {
        this.opened.update((prev: boolean) => !prev);
      }
    }
    

    For example, if we want to open the dialog, from the root component, just inject the token and toggle it for the dialog to open.

    @Component({
      selector: 'my-app',
      template: `
        <button kendoButton (click)="open()">Show Dialog</button>
        <button kendoButton (click)="close()">Close Dialog</button>
        <app-dialog/>
      `,
      standalone: false,
    })
    export class AppComponent {
      opened: WritableSignal<boolean> = inject(DIALOG_OPEN);
    
      public close(): void {
        this.opened.set(false);
      }
    
      public open(): void {
        this.opened.set(true);
      }
    }
    

    Full Code:

    import {
      Component,
      inject,
      InjectionToken,
      signal,
      WritableSignal,
    } from '@angular/core';
    import {
      DialogComponent,
      DialogActionsComponent,
    } from '@progress/kendo-angular-dialog';
    
    export const DIALOG_OPEN = new InjectionToken('DIALOG_OPEN', {
      providedIn: 'root',
      factory: () => signal(false),
    });
    
    @Component({
      selector: 'app-dialog',
      imports: [DialogComponent, DialogActionsComponent],
      template: `
      @if(opened()) {
      <kendo-dialog title="Oh no!" (close)="close()">
        <p style="margin: 30px; text-align: center;">Dialog was opened.</p>
    
        <kendo-dialog-actions>
          <button kendoButton (click)="close()" themeColor="primary">
            Close
          </button>
        </kendo-dialog-actions>
      </kendo-dialog>
      }
      `,
    })
    export class DialogCustomComponent {
      opened: WritableSignal<boolean> = inject(DIALOG_OPEN);
      close() {
        this.opened.update((prev: boolean) => !prev);
      }
    }
    
    @Component({
      selector: 'my-app',
      template: `
        <button kendoButton (click)="open()">Show Dialog</button>
        <button kendoButton (click)="close()">Close Dialog</button>
        <app-dialog/>
      `,
      standalone: false,
    })
    export class AppComponent {
      opened: WritableSignal<boolean> = inject(DIALOG_OPEN);
    
      public close(): void {
        this.opened.set(false);
      }
    
      public open(): void {
        this.opened.set(true);
      }
    }
    

    Stackblitz Demo