Angular2 with boot events does not cause observable changes

I found a strange problem with bootstrapjs models along with angular2. I have a component related to the html boot mod mod:

// ... lots of imports here @Component({ selector: 'scrum-sessions-modal', bindings: [ScrumSessionService, ScrumSessionRepository, ScrumModuleDataContext, ElementRef] }) @View({ templateUrl: 'templates/modals/scrumSessions.modal.html', styleUrls: [''] }) export class ScrumSessionsModalComponent { private _elementRef: ElementRef; public sessions: ScrumSession[]; constructor(scrumSessionsProxy: ScrumSessionsProxy, scrumSessionService: ScrumSessionService, elementRef: ElementRef) { this._proxy = scrumSessionsProxy; this._scrumSessionService = scrumSessionService; this._elementRef = elementRef; // This is the bootstrap on shown event where i call the initialize method jQuery(this._elementRef.nativeElement).on("shown.bs.modal", () => { this.initialize(); }); jQuery("#scrumSessionsModal").on("hidden.bs.modal", () => { this.cleanup(); }); } public initialize() { // Start our hub proxy to the scrumsessionshub this._proxy.start().then(() => { // Fetch all active sessions; this._scrumSessionService.fetchActiveSessions().then((sessions: ScrumSession[]) => { // This console.log is being shown, however the sessions list is never populated in the view even though there are active sessions! console.log("loaded"); this.sessions = sessions; }); }); } // Free up memory and stop any event listeners/hubs private cleanup() { this.sessions = []; this._proxy.stop(); } } 

In this modal, I have an event listener in the constructor that checks when the modal is shown. when it calls the initialization function, which will load the source data, which will be displayed in modal mode.

The html for modal is as follows:

 <div class="modal fade" id="scrumSessionsModal" tabindex="-1" role="dialog" aria-labelledby="scrumSessionsModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> <h4 class="modal-title" id="myModalLabel">Actieve sessies </h4> </div> <div class="modal-body"> <table class="table"> <tr> <th>Id</th> <th>Maker</th> <th>Start datum</th> <th>Aantal deelnemers</th> </tr> <tr *ngFor="#session of sessions"> <td> {{ session.id }} </td> <td> test </td> <td> test </td> <td> test </td> </tr> </table> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> </div> </div> </div> 

The problem I am facing is that the session variable populated by the fetchactivesessions function is not updated in the view. I put a debugger in the method of completing the selection and is called with the result of a single scrumsession object.

However, when I remove the on event listener ("show.bs.modal") and just put the initialize () call inside the setTimeout function for 5 seconds, and then open the modal list of sessions that populates correctly.

This only happens when I place an initialization call inside the callback of the displayed bootstrap event. It looks like it stops checking for changes when the call is inside the jquery callback. Does anyone know what a deal is here? I can say that you simply call initialization in the constructor, but I prefer it to fire the event shown from the bootstrap.

I published the latest version of the debugging application at: http://gbscrumboard.azurewebsites.net/

+2
javascript jquery angular twitter-bootstrap typescript
source share
3 answers

Wrapped above solution with easy to use service:

BS-events.service.ts

 import { Subject, Observable, Subscription } from 'rxjs'; import { Injectable } from '@angular/core'; @Injectable() export class BsEventsService { private static BS_COLLAPSIBLE_SHOWN: string = 'shown.bs.collapse'; private static BS_COLLAPSIBLE_HIDDEN: string = 'hidden.bs.collapse'; constructor() { this.registerEvent(BsEventsService.BS_COLLAPSIBLE_SHOWN); this.registerEvent(BsEventsService.BS_COLLAPSIBLE_HIDDEN); } onCollapsibleShown(): Observable<Event> { return Observable.fromEvent(document, BsEventsService.BS_COLLAPSIBLE_SHOWN); } onCollapsibleHidden(): Observable<Event> { return Observable.fromEvent(document, BsEventsService.BS_COLLAPSIBLE_HIDDEN); } private registerEvent(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> function eventRelay(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'; .. private onCllapsibleShownSubject: Subscription; private onCllapsibleHiddenSubject: 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(); } 
+1
source share

Solution: Observe bootstrap events with Angular4 / Bootstrap4.

Summary: Capturing bootstrap events and disabling a special event in its place. Custom event will be observed from angular components.

Index.html:

 <body> ... <script> function eventRelay(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 component / service:

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

In your component constructor or ngOnInit (), you can now log and monitor boot events. For example, the "hidden" event Bootstrap Modal.

 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 for the DOM to load it. Otherwise, it will not be recognized if you call the eventRelay calls from registerEvent.

Conclusion: this is a middleware solution that works with vanilla Angular4 / Bootstrap4. I do not know why bootstrap events are not displayed inside angular, and I did not find another solution.

Note1: Register the register only once for each event. This means β€œonce” throughout the application, so consider entering all registerEvent calls into app.component.ts. Calling registerEvent several times will cause recurring events to be emitted before angular.

Note2: There is an alternative bootstrap structure that you can use with angular called ngx-bootstrap ( https://valor-software.com/ngx-bootstrap/#/ ), which can do but I have not tested this.

Advanced solution. Create an angular service that manages events registered through "registerEvent", so for each event it only calls "registerEvent". Thus, in your components, you can call "registerEvent" and then immediately create an Observable for this event, without worrying about duplicate calls to "registerEvent" in other components.

+5
source share

Decided by the link provided by Mark. Apparently angular is unaware of boot 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(); }); }); 
+2
source share

All Articles