带引导事件的Angular2不会触发可观察到的更改

时间:2015-12-28 14:24:15

标签: javascript jquery twitter-bootstrap typescript angular

我发现了一个涉及bootstrapjs模态以及angular2的奇怪问题。 我有一个绑定到bootstrap模式的html的组件:

// ... 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();
    }

}

在这个模态中,我在构造函数中有一个事件监听器,用于检查何时显示模态。当它这样做时,它调用初始化函数,该函数将加载将在模态中显示的初始数据。

模态的html如下所示:

<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>

我遇到的问题是由fetchactivesessions函数填充的sessions变量在视图中不会更新。我在调用完成方法上放了一个调试器,并且调用了一个scrumsession对象的结果。

但是,当我删除on(“shown.bs.modal”)事件监听器并简单地将调用initialize()放入5秒的setTimeout函数内,然后打开模式时会正确填充会话列表。 / p>

只有当我将initialize调用放在bootstrap显示事件的回调中时才会发生。当调用在jquery回调中时,它就像停止检查更改一样。有谁知道这笔交易是什么?我可以只在构造函数中调用初始化,但我宁愿调用bootstrap中显示的事件。

我已在适用于http://gbscrumboard.azurewebsites.net/

的调试版中发布了最新版本的应用程序

3 个答案:

答案 0 :(得分:6)

解决方案:使用Angular4 / Bootstrap4观察Bootstrap事件。

摘要:拦截引导事件,并在其位置触发自定义事件。自定义事件将在角度组件内观察。

的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>

在您的组件/服务中:

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

在Component的构造函数或ngOnInit()中,您现在可以注册并观察引导事件。例如,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...
    });
}

注意:'eventRelay'函数必须在index.html内,以便DOM加载它。否则,当您从'registerEvent'中发出对'eventRelay'的调用时,将无法识别它。

结论:这是一个适用于vanilla Angular4 / Bootstrap4的中间件解决方案。我不知道为什么bootstrap事件在angular中是不可见的,我还没有找到任何其他解决方案。

注1:每次事件只调用一次registerEvent。这意味着在整个应用程序中“一次”,因此请考虑将所有registerEvent调用放在app.component.ts中。多次调用registerEvent将导致重复事件被发送到角度。

注意2:你可以使用一个替代的bootstrap框架,可以使用名为ngx-bootstrap(https://valor-software.com/ngx-bootstrap/#/)的角度,这可能会使引导事件可见,但我还没有测试过。

高级解决方案:创建一个Angular Service来管理通过'registerEvent'注册的事件,因此它只为每个事件调用一次'registerEvent'。这样,在组件中,您可以调用'registerEvent',然后立即为该事件创建Observable,而不必担心在其他组件中重复调用'registerEvent'。

答案 1 :(得分:2)

使用Mark提供的链接解决了这个问题。 显然,angular不知道bootstrap事件,我必须告诉zone.js手动触发更改检测。

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

答案 2 :(得分:2)

使用易于使用的服务包含在解决方案之上:

更新:如果您使用的是 rxjs-6 ,则需要进行重大更改,您必须

import { fromEvent } from 'rxjs';
..

并使用

..
fromEvent(document, BsEventsService.BS_COLLAPSIBLE_SHOWN);

而不是

Observable.fromEvent(document, BsEventsService.BS_COLLAPSIBLE_SHOWN);

<强> 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>

<强>组件

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