Michael Bromley
Michael Bromley

Reputation: 4822

Angular2: Using DynamicComponentLoader from a service

Use case: I am building a toast notification service, ToastService. I want to be able to inject this service into components to create new toasts.

I have created a Toast component which represents a single notification:

@Component({
    selector: 'toast',
    template: `<div>This is a notification</div>`
})
export class Toast {}

And the I want to be able to call toastService.show(myMessage); to display a toast from one of my components.

So far my ToastService looks like this:

@Injectable()
export class ToastService{

    constructor(private loader: DynamicComponentLoader) {}

    show(message: string) {
        this.loader.loadNextToLocation(
            Toast,
            /* location: ElementRef - how do I get one? I want to load it into the body */
        )
        .then((ref: ComponentRef) => {
            console.log('loaded toast!');
        });
    }
}

I would ideally like to load the toast as a child of the body element, to ensure it is always on top of the rest of the app elements.

How is this possible? Or is there a better approach? I tried to figure out how they do it in the angular2 material repo, but it is just too hard for me to understand.

Upvotes: 1

Views: 534

Answers (2)

Chandermani
Chandermani

Reputation: 42669

Look at angular-modal project. The requirements for modal are similar to modal dialog.

Specifically check modal.ts file, how open method gets hold of the root component to show a dialog.

public open(componentType: FunctionConstructor, bindings: ResolvedProvider[],
                config?: ModalConfig): Promise<ModalDialogInstance> {
        // TODO: appRef.injector.get(APP_COMPONENT) Doesn't work.
        // When it does replace with the hack below.
        //let myElementRef = this.appRef.injector.get(APP_COMPONENT).location;
        let elementRef: ElementRef = (<any>this.appRef)._rootComponents[0].location;

        return this.openInside(componentType, elementRef, null, bindings, config);
    }

appRef is ApplicationRef class injected in costructor and stores the reference to the running application.

  constructor(private componentLoader: DynamicComponentLoader, private appRef: ApplicationRef,
                @Optional() defaultConfig: ModalConfig) {

Upvotes: 1

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657308

I wouldn't try to get ElementRef into a service. You wouldn't even know if the ElementRef is still valid when you try to use it.

Instead I would add a Toast component "somewhere" that listens to updates from an Observable in ToastService and displays the components it gets passed from the Observable as events.

If you want the Toast component to be added outside of your AppComponent you can bootstrap it as a distinct Angular application.

In this case you need to create the shared service instance before bootstrapping and provide the same instance to both "applications" like:

let toastService = new ToastService();
bootstrap(AppComponent, [provide(ToastService, {useValue: toastService})]);
bootstrap(Toast, [provide(ToastService, {useValue: toastService})]);

Upvotes: 1

Related Questions