带有引导事件的 Angular2 不会触发可观察到的变化

Posted

技术标签:

【中文标题】带有引导事件的 Angular2 不会触发可观察到的变化【英文标题】:Angular2 with bootstrap events does not trigger observable changes 【发布时间】:2016-04-02 09:47:54 【问题描述】:

我发现了一个涉及 bootstrapjs 模态和 angular2 的奇怪问题。 我有一个绑定到引导模式的 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 函数填充的会话变量没有在视图中更新。我在 fetch 完成方法上放置了一个调试器,并使用一个 scrumsession 对象的结果调用它。

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

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

我已在http://gbscrumboard.azurewebsites.net/发布了用于调试的最新版本应用程序

【问题讨论】:

你能检查一下this.sessions = sessions;所在的this是否实际上是指ScrumSessionsModalComponent实例? 我已经发布了最新版本并在 fetch promise 上放置了一个调试器。 gbscrumboard.azurewebsites.net 。 (单击活动会话按钮)。在那里你可以看到它是正确的实例(至少从我所见) 你试过使用 Observables 来监听事件吗?喜欢Observable.fromEvent(this._elementRef.nativeElement, 'shown.bs.modal') 我以前没有听说过。我已经尝试如下:var obs = Observable.fromEvent(this._elementRef.nativeElement, 'shown.bs.modal'); obs.forEach((value: any) =&gt; this.initialize(); , null); 但是这并没有更新视图,但也许我用错了。 这听起来像是一个区域问题——Angular 不知道 jQuery 回调,所以它无法对其进行修补。看看这个答案是否有帮助:***.com/a/34376204/215945 【参考方案1】:

用易于使用的服务包装上述解决方案:

更新:如果您使用的是 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();

【讨论】:

我回过头来看我的答案,感觉还是一样的......但一年前我不一样了,当然角度也不一样。改变了我的方法,here 是隐藏/显示引导模式的更好解决方案。【参考方案2】:

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

总结:拦截引导事件,并在其位置触发自定义事件。自定义事件可以从 Angular 组件中观察到。

索引.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);
  

在组件的构造函数或 ngOnInit() 中,您现在可以注册并观察引导事件。例如,Bootstrap Modal 'hidden' 事件。

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。我不知道为什么引导事件在 Angular 中不可见,而且我还没有找到任何其他解决方案。

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

注意 2:您可以使用另一种引导框架,称为 ngx-bootstrap (https://valor-software.com/ngx-bootstrap/#/),它可能使引导事件可见,但我尚未对此进行测试。

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

【讨论】:

这应该是 Angular 4(肯定是 4.2.4)的答案。【参考方案3】:

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

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

【讨论】:

似乎不适用于 Angular4 / Bootstrap4。在这里尝试了您的答案以及上面的“可观察”版本,但它们都不会导致 Angular4 从 Bootstrap4 接收到“shown.bs.modal”事件。 是的,这个答案现在已经过时了。这仍处于 angular 2 beta 阶段。我不知道如何将其转换为角度 4。 我找到了解决方案。从 index.html 中的脚本中拦截事件;构造一个与引导事件具有相同事件名称的 customEvent,并从引导事件的原始目标发出事件。这样做可以让您从组件中观察事件。

以上是关于带有引导事件的 Angular2 不会触发可观察到的变化的主要内容,如果未能解决你的问题,请参考以下文章

手动编辑日期或清除日期时,引导日期选择器更改日期事件不会触发

可观察到的 forkJoin 没有触发

带有 mousedown 事件处理程序的 AngularJS 指令,但单击事件永远不会触发

带有 Meteor 的 Blaze-Apollo,观察者不会触发变量更改的订阅

带有引导程序的Angular2动画

Angular2 beta.12 和 RxJs 5 beta.3 的可观察到的错误