9

I basically want to create a custom dialog component that I can utilize from anywhere in my Angular2 app regardless of where the using component is in the application tree. For simplicity lets call this my SayHello Component.

Consider the following application tree:enter image description here

So let's say i want SomeComponent.level3.component to call up the dialog in SayHello.component.

In Angular 1.x I would inject RootScope into a controller and light up a dialog that way. Now, I understand (more or less) that for Angular2 you can bubble events (with event emitters) up the component tree, but it seems tedious to bubble an event all the way from SomeComponent.level3.component up the tree and down to SayHello.component.

So I thought I would create a SayHello Service that I would inject anywhere I wanted to light up my dialog. Here is a sketch of the code I have formulated.

myApp.component.ts

import {SayHelloComponent} from "<<folder>>/sayHello.component";
import {BunchOfComponents} from "<<folder>>/bunchOfComponents";

@Component({
    directives: [SayHelloComponent],
    selector: "my-app",
    templateUrl: `<bunch-of-components>Within this component exists
                      SomeComponent.level3.component </bunch-of-components>
                      <say-hello showdialog="{{showDialog}}" message="{{message}}">
                      </say-hello>`

})
export class myAppComponent {
    showDialog = false;
    message = "";

    constructor(private sayHelloService: SayHelloService) {
        this.showDialog = sayHelloService.showDialog;
        this.message = sayHelloService.message;

    }
}

SayHelloService.ts

import {Injectable} from 'angular2/core';

@Injectable()
export class SayHelloService {
    public showDialog: boolean = false;
    public message: string ="";

    constructor() {

    }

}

SayHello.component.ts

import {Component} from "angular2/core";
import {SayHelloService} from "<<folder>>/SayHelloService";
@Component({
    directives: [],
    selector: "say-hello",
    template: "[do hello component]"
})
export class SayHelloComponent {
    @Input() showdialog: boolean;
    @Input() message: string;

       constructor(private sayHelloService: SayHelloService) {

    }
    //This idea here is to detect change in showDialog
    //If true then do an alert with the message
    ngOnChanges(changes: { [propName: string]: SimpleChange }) {
        var obj = changes["showdialog"];
        if (obj !== null) {
            if (changes["showdialog"].currentValue === true) {
                alert(this.message);
                this.sayHelloService.showDialog = false;
            }

        };
    }

}

SomeComponent.level3.component

import {Component} from "angular2/core";
import {SayHelloService} from "<<folder>>/SayelloService";

@Component({
    directives: [],
    selector: "some-component",
    template: "<button (click)='doHello()'>Do say hello</button>"
})
export class PageContactUsComponent {

    constructor(private sayHelloService: SayHelloService) {

    }


    doHello(): void {
        this.sayHelloService.message = "Hello world";
        this.sayHelloService.showDialog = true;
    }
}

appBoot.ts

import {bootstrap} from "angular2/platform/browser";
import {MyAppComponent} from "<<folder>/MyAppComponent";
import {SayHelloService} from "<<folder>>/SayHelloService";

bootstrap(MyAppComponent, [
    SayHelloService
]);

Needless to say, this doesn't work. I don't get any errors, but the SayHello.component does not detect any change in the value of 'showdialog'...so nothing happens. Any ideas as to how to do properly do this would be much appreciated.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
brando
  • 8,215
  • 8
  • 40
  • 59
  • 2
    Rather than (or perhaps in addition to) posting all these code snippets individually, having a plunkr with all these working parts together would make it much easier for me/us to play around with your implementation and show you what needs to change – drew moore Jan 03 '16 at 01:51
  • #drewmoore, good idea will work it in plunkr. #pixelbits, yes that is my strategy, but I am doing something wrong. – brando Jan 03 '16 at 01:54
  • FYI, custom events (by event emitter) cannot be bubbled up (only DOM events can). – Michael Kang Jan 03 '16 at 01:59
  • 3
    Put an EventEmitter inside the service. Put a `showDialog()` API/method on the service that other components can call. The `showDialog()` method should `emit()` an event. Your dialog component can subscribe to the event and unhide/show itself when it receives an event. To wrap an EventEmitter in a service, see [this answer](http://stackoverflow.com/questions/34376854/delegation-eventemitter-in-angular2/34402436#34402436). – Mark Rajcok Jan 03 '16 at 02:08
  • #Mark Rajcok, wow your solution worked like a charm! – brando Jan 03 '16 at 02:53
  • brando, FYI, use @ instead of # when you refer to usernames, otherwise people will not get notified when you mention them in a comment. – Mark Rajcok Jan 03 '16 at 03:00
  • @MarkRajcok got it on the tip regarding # vs @. Thanks! – brando Jan 03 '16 at 04:13
  • 1
    Here is a bootstrap modal implementation that loads the content dynamically using DynamicComponentLoader. The modal component exists under the Root component, and the ExampleContent component can loaded from anywhere: http://plnkr.co/edit/xjXnHyHX2jrj7Nsp9h8H?p=preview. To be useful, it would need to be tweaked to support events, etc. – Michael Kang Jan 03 '16 at 08:10
  • @pixelbits nice solution. DynamicComponentLoader is definitely a new beast for me. – brando Jan 03 '16 at 17:36

2 Answers2

8

As mentioned in a comment above,

  • Put an Observable inside the service (note, not an EventEmitter)
  • Put a showDialog() API/method on the service that other components can call. The showDialog() method should call next() to send an event.
  • Your dialog component can subscribe to the event and unhide/show itself when it receives an event.

To wrap an Observable in a service, see this answer.

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • Wish I saw this before communicating between components started hurting my brain (: I came up with [this solution](http://stackoverflow.com/a/34576997/1876949) (passing emitter as @Input). I think it's a bit more reusable, because I don't have to import the service into components that might not need it, and can move component between projects more freely. – Sasxa Jan 03 '16 at 13:57
1

An alternative to using a service is dynamically creating the component using ViewContainerRef/createComponent described here:

https://www.lucidchart.com/techblog/2016/07/19/building-angular-2-components-on-the-fly-a-dialog-box-example/

Alexander Taylor
  • 16,574
  • 14
  • 62
  • 83