1

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.

2 Answers 2

1

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) {
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks - this is the kind of think I have been attempting. Looks good, but I am concerned about the use of a private API.
And for this very reason angular-modal has this warning :) You too can have a TODO.
Actually there is another hack where you can store the root component reference in some global variable and use that reference. No DI involved.
In this case, I am building a re-usable library so I cannot rely on things like storing global references to the root component. Your answer is probably the correct answer to my question, so I will mark it as such. However, I get the feeling my question (and this approach) may not be the "angular 2 way" to do this. Hard to tell with so few canonical examples out there.
Angular2 still does not have a easy mechanism to augment the component tree dynamically.What @Gunter is mention is a better solution but i believe requires one to inject the Toast component into the tree dynamically (as it is a library)
1

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})]);

1 Comment

Perhaps I am thinking too much in terms of my ng1 experience. The solution with an external component which listens to a stream sounds promising - I'll attempt a solution based on that. Although this answer may prove to be the correct solution for my overall goal, I think the other one from @Chandermani actually answers the question. Probably a case of an "XY problem" meta.stackexchange.com/a/66378

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.