Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
240 views
in Technique[技术] by (71.8m points)

javascript - 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({
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 does it calls the initialize function that will load the initial data that will be displayed in the modal.

The html for the modal looks like this:

<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 having is that the sessions variable that is filled by the fetchactivesessions function does not get updated in the view. I put a debugger on the fetch completion method and it is being called with a result of one scrumsession object.

However, when i remove the on("shown.bs.modal") event listener and simply put the call to initialize() inside a setTimeout function of 5 seconds and then open the modal the sessions list is correctly populated.

It only happens when i put the initialize call inside the callback of the bootstrap shown event. It is like it stops checking for changes when the call is inside a jquery callback. Does anyone know what the deal is here? I could offcourse just call the initialize in the constructor but i rather have it called on the shown event from bootstrap.

I have published the latest version of the app for debugging at: http://gbscrumboard.azurewebsites.net/

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Solution: 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>
  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 your 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'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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...