ngx-bootstrap: fix(positioning): angular 2.2 modal.show is throwing issue

Angular v2.2 broke our get root view component ref hack temporary workaround:

  1. Add ComponentsHelper to AppModule (root module)
import {ComponentsHelper} from 'ng2-bootstrap/ng2-bootstrap'
// ...
providers: [{provide: ComponentsHelper, useClass: ComponentsHelper}],
  1. Set root view component ref explicitly in AppComponent (root component)
  public constructor(componentsHelper:ComponentsHelper, vcr:ViewContainerRef) {
    componentsHelper.setRootViewContainerRef(vcr);
  }

I am facing an issue with my ng2-bootstrap modals. For some reason, I am not able to show them.

Here is my HTML :

<button (click)="testModal.show()">Show this modal</button>

<div bsModal
     #testModal="bs-modal"
     class="modal fade"
     tabindex="-1"
     role="dialog"
     aria-hidden="true">
    <div class="modal-dialog modal-md">
        <div class="modal-content">
            <div class="modal-body">
                TEST
            </div>
        </div>
    </div>
</div>

Here is my NgModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { MODAL_DIRECTIVES } from 'ng2-bootstrap/ng2-bootstrap';

@NgModule({
    declarations: [ MODAL_DIRECTIVES],
    exports:      [ MODAL_DIRECTIVES],
    imports:      [ BrowserModule,
                    FormsModule,
                    HttpModule ],
})
export class SharedModule {}

I see this error in the log console : Error: Uncaught (in promise): Token must be defined! and the modal does not show up

I am running Angular 2 RC5. Is there something I am missing here?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 8
  • Comments: 65 (31 by maintainers)

Commits related to this issue

Most upvoted comments

Thanks @kraikill

For me only your solution works. Its also the easiest patch for now.

Just to clarify for others, copy/paste the following in your main component (appComponent)

import { ComponentsHelper } from 'ng2-bootstrap/ng2-bootstrap'

ComponentsHelper.prototype.getRootViewContainerRef = function () {
    // https://github.com/angular/angular/issues/9293
    if (this.root) {
        return this.root;
    }
    var comps = this.applicationRef.components;
    if (!comps.length) {
        throw new Error("ApplicationRef instance not found");
    }
    try {
        /* one more ugly hack, read issue above for details */
        var rootComponent = this.applicationRef._rootComponents[0];
        //this.root = rootComponent._hostElement.vcRef;
        this.root = rootComponent._component.viewContainerRef;
        return this.root;
    }
    catch (e) {
        throw new Error("ApplicationRef instance not found");
    }
};

it should fix the issue for NG2 v2.2.1 until an official patch is released by NG2-bootstrap team 😃

ng2 v2.2 added a breaking change to internals working on it

So after talking to @valorkin he let me know that the answer (for now) is at the very top of this thread. I just failed at reading the entire thing lol. For anyone not wanting to read through the whole post you need to do two things.

Add the following to your constructor.

constructor(private componentsHelper: ComponentsHelper, private vcr: ViewContainerRef) {
    componentsHelper.setRootViewContainerRef(vcr);
  }

And add the following to your imports

import { ViewContainerRef } from '@angular/core';
import { ComponentsHelper } from 'ng2-bootstrap';

https://github.com/valor-software/ng2-bootstrap/issues/986#issue-177218652

@valorkin is there any reason not to make a <modal-outlet></modal-outlet> like the angular router does? This will be really clear to everybody and you would stop calling it the hack because it is just how angular does it.

Then it is a component where all the modals (or at least the backdrop) can be rendered in isolated environment. Everybody is happy and it won’t ever be a problem again.

e.g. app component template would be similar to

<header>/<header>
<router-outlet>/<router-outlet>
<footer>/<footer>
<modal-outlet>/<modal-outlet>

Maybe the same could be applied for the tooltips

Hi,

I am also seeing the same issue. It seems to be a timing issue. When I do the following:

import { Component, ViewContainerRef, AfterViewInit, ViewChild } from '@angular/core';
import { LoginModalComponent } from './login-modal/login-modal.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements AfterViewInit {
  private viewContainerRef: ViewContainerRef;

  @ViewChild(LoginModalComponent)
  public loginModal: LoginModalComponent;

  title = 'Hello World!';

  public constructor(viewContainerRef: ViewContainerRef) {
    this.viewContainerRef = viewContainerRef;
  }

  public ngAfterViewInit() {
    this.loginModal.modal.show();
  }
}

Then I get the below error:

error_handler.js:53 Error: ApplicationRef instance not found
    at ComponentsHelper.getRootViewContainerRef (components-helper.service.js:50)
    at ComponentsHelper.appendNextToRoot (components-helper.service.js:94)
    at ModalDirective.showBackdrop (modal.component.js:191)
    at ModalDirective.show (modal.component.js:108)
    at AppComponent.ngAfterViewInit (app.component.ts:22)
    at _View_AppComponent_Host0.detectChangesInternal (host.ngfactory.js:36)
    at _View_AppComponent_Host0.AppView.detectChanges (view.js:219)
    at _View_AppComponent_Host0.DebugAppView.detectChanges (view.js:324)
    at ViewRef_.detectChanges (view_ref.js:130)
    at application_ref.js:437

It seems that there are no components registered in this.applicationRef.components at this stage, which is why the above error occurs… Moving the this.loginModal.modal.show(); into a click directive, then it works fine…

@valorkin @conor-mac-aoidh usually +1 is not the best thing to do but I cannot avoid it. Thank you for your contribution to the development of angular2 and its community šŸ‘ ! I believe this project helps lots of people and makes angular2 closer to the not experienced users. Don’t listen to the ugly people, listen to the good ones 😃

Just wanted to verify https://github.com/escarabin. Facing a similar error message with ng2-bootstrap version 1.1.12 the moment I try to invoke: someModal.show();

Error in some.component.html caused by: ApplicationRef instance not found

export class SomeComponent {
    @ViewChild('someModal') public someModal: ModalDirective;

    public show():void { this.someModal.show(); }

    public hide():void { this.someModal.hide(); }
}

Works fine when loaded in the browser, but fails when executed in a test run by karma. The interesting thing is: Running the following while testing does not lead to any errors: someModal.hide(); I’m sure I miss something for karma.conf.js or karma.shim.js but I do not have any idea what it could be.

I updated to v1.1.6 tough. The error has now changed to ā€œapplicationRef instance not foundā€ when I try to open a modal.

@valorkin Thanks for your time on this project it is very helpful to have this module! Keep up the good work!

I rewrote component injector, testing… will be able to publish late today or early tomorrow

Simple workaround: override - ComponentsHelper.getRootViewContainerRef

import { ComponentsHelper } from "ng2-bootstrap/ng2-bootstrap";
ComponentsHelper.prototype.getRootViewContainerRef = function (): ViewContainerRef { 
...
}

The problem of access to the to undefined _hostElement and we need to get the correct ViewContainerRef. Replace:

this.root = rootComponent._hostElement.vcRef;

on (if viewContainerRef exists in app component)

this.root = rootComponent._component.viewContainerRef;

This solution works fine for me

same here, amazing job and THANKS for the effort, sure is appreciated!! Sean

While the given workaround did not fix the issue, here is the version that works for me.

import { Component, ViewContainerRef } from '@angular/core';
import { ComponentsHelper } from 'ng2-bootstrap/ng2-bootstrap';

ComponentsHelper.prototype.getRootViewContainerRef = function () {
    // https://github.com/angular/angular/issues/9293
    if (this.root) {
        return this.root;
    }
    var comps = this.applicationRef.components;
    if (!comps.length) {
        throw new Error("ApplicationRef instance not found");
    }
    try {
        /* one more ugly hack, read issue above for details */
        var rootComponent = this.applicationRef._rootComponents[0];
        //this.root = rootComponent._hostElement.vcRef;
        this.root = rootComponent._component.viewContainerRef;
        return this.root;
    }
    catch (e) {
        throw new Error("ApplicationRef instance not found");
    }
};

@Component({
  selector: '......',
  template: "......"
})
export class AppComponent {
  // As instructed at http://valor-software.com/ng2-bootstrap/#/modals
  private viewContainerRef: ViewContainerRef;
  public constructor(viewContainerRef: ViewContainerRef) {
    // You need this small hack in order to catch application root view container ref
    this.viewContainerRef = viewContainerRef;
  }
}

ok, guys new injection service is working šŸ˜‰ so I will be updating components to use new service most probably I will do release with next tag, on Monday when QA will approve this version it will marked as latest

I agree wtih @smnbbrv. The outlet approach seems much more robust. Having core functionality depend on brittle hacks really shakes my confidence in the whole framework. The workaround suggested is also not working for me, as I am using system.js, and migrating to webpack is not an option for me.

Really sorry about that. I just updated to v1.1.5 and the error has changed to ā€œCannot read property ā€˜instance’ of undefinedā€

Here is a full screenshot of the log: screen shot 2016-09-30 at 11 39 19

I hope it is not too late to request some help on this…

Forget about directives, use exported modules