Skip to content Skip to sidebar Skip to footer

Angular2 With Bootstrap Events Does Not Trigger Observable Changes

I found a weird issue involving bootstrapjs modals along with angular2. I have a component that is bound to the html of an bootstrap modal: // ... lots of imports here @Component(

Solution 1:

Answer : Observing Bootstrap Events with Angular4 / Bootstrap4.

Summary: Intercept the bootstrap events, and fire off a custom event in its place. The custom event will be observable from within angular components.

Index.html:

<body>
...
<script>functioneventRelay(bs_event){
  $('body').on(bs_event, function($event){
    const customEvent = document.createEvent('Event');
    customEvent.initEvent(bs_event, true, true);
    let target_id = '#'+$event.target.id;
    $(target_id)[0].dispatchEvent(customEvent);
  });
</script></body>

In your Component/Service:

//dynamically execute the event relaysprivateregisterEvent(event){
    var script = document.createElement('script');
    script.innerHTML = "eventRelay('"+event+"');"document.body.appendChild(script);
  }

In your Component's Constructor or ngOnInit(), you can now register and observe the bootstrap events. Eg, Bootstrap Modal 'hidden' event.

constructor(){
    registerEvent('hidden.bs.modal');
     Observable.fromEvent(document, 'hidden.bs.modal')
     .subscribe(($event) => {
       console.log('my component observed hidden.bs.modal');
       //...insert your code here...
    });
}

Note: the 'eventRelay' function must be inside index.html so that the DOM loads it. Otherwise it will not be recognized when you issue the calls to 'eventRelay' from within 'registerEvent'.

Conclusion: This is a middle-ware workaround solution that works with vanilla Angular4/Bootstrap4. I don't know why bootstrap events are not visible within angular, and I have not found any other solution around this.

Note1: Only call registerEvent once for each event. That means 'once' in the entire app, so consider putting all registerEvent calls in app.component.ts. Calling registerEvent multiple times will result in duplicate events being emitted to angular.

Note2: There is an alternative bootstrap framework you can use with angular called ngx-bootstrap (https://valor-software.com/ngx-bootstrap/#/) which might make the bootstrap events visible, however I have not tested this.

Advanced Solution: Create an Angular Service that manages the events registered through 'registerEvent', so it only only call 'registerEvent' once for each event. This way, in your components you can call 'registerEvent' and then immediately create the Observable to that event, without having to worry about duplicate calls to 'registerEvent' in other components.

Solution 2:

Wrapped above solution with an easy-to-use-service:

Update: If you are using rxjs-6 there are breaking changes, you will have to

import { fromEvent } from'rxjs';
..

and use

..
fromEvent(document, BsEventsService.BS_COLLAPSIBLE_SHOWN);

instead of

Observable.fromEvent(document, BsEventsService.BS_COLLAPSIBLE_SHOWN);

bs-events.service.ts

import { Subject, Observable, Subscription } from'rxjs';
import { Injectable } from'@angular/core';

@Injectable() exportclassBsEventsService {   privatestaticBS_COLLAPSIBLE_SHOWN: string = 'shown.bs.collapse';   privatestaticBS_COLLAPSIBLE_HIDDEN: string = 'hidden.bs.collapse';

  constructor() {
    this.registerEvent(BsEventsService.BS_COLLAPSIBLE_SHOWN);
    this.registerEvent(BsEventsService.BS_COLLAPSIBLE_HIDDEN);
  }

  onCollapsibleShown(): Observable<Event> {
      returnObservable.fromEvent(document, BsEventsService.BS_COLLAPSIBLE_SHOWN);
  }   
  onCollapsibleHidden(): Observable<Event> {
      returnObservable.fromEvent(document, BsEventsService.BS_COLLAPSIBLE_HIDDEN);
  }

  privateregisterEvent(event) {
    var script = document.createElement('script');
    script.innerHTML = "eventRelay('" + event + "');"document.body.appendChild(script);
  }

}

index.html

<app-root></app-root>
..
  <!-- relay bootstrap events to angular - start --><script>functioneventRelay(bs_event) {
      $('body').on(bs_event, function ($event) {
        const customEvent = document.createEvent('Event');
        customEvent.initEvent(bs_event, true, true);
        let target_id = '#' + $event.target.id;
        $(target_id)[0].dispatchEvent(customEvent);
      });
    }
  </script><!-- relay bootstrap events to angular - end -->
..
</body>

component

import { BsEventsService } from'./service/bs-events.service';
import { Subject, Observable, Subscription } from'rxjs';
..

privateonCllapsibleShownSubject: Subscription;
privateonCllapsibleHiddenSubject: Subscription;

..
constructor(.., private bsEventsService: BsEventsService) {
..
}

ngOnInit() {
    this.onCllapsibleShownSubject = this.bsEventsService.onCollapsibleShown().subscribe((event: any) => {
      console.log('shown: ' + event.target.id);
    });
    this.onCllapsibleHiddenSubject = this.bsEventsService.onCollapsibleHidden().subscribe((event: any) => {
      console.log('hidden: ' + event.target.id);
    });
}
ngOnDestroy() {
    this.onCllapsibleShownSubject.unsubscribe();
    this.onCllapsibleHiddenSubject.unsubscribe();
}

Solution 3:

Solved it with the link provided by Mark. Apparently angular does not know about the bootstrap events and i have to tell zone.js to manually trigger change detection.

this._elementRef = elementRef;
jQuery(this._elementRef.nativeElement).on("shown.bs.modal", () => {
    this._zone.run(() => {
      this.initialize();    
    });
});

Post a Comment for "Angular2 With Bootstrap Events Does Not Trigger Observable Changes"