Search code examples
angularjsangularwebpacksystemjsangular-cli

angular 2 change detection behaves differently with cli & webpack


We're having an issue with our app that's proving very difficult to pin down. At this point, I'm relatively convinced that there is a bug somewhere in angular (or zone, or webpack, or angular-cli), but the complexity of the app required to reproduce makes it a bit prohibitive to post as a bug report or plunk. So I thought I'd post here hoping that someone can offer some insight who's dealt with similar challenges.

The page we're loading contains multiple components which interact with one another, but I'll try to keep the description of the issue as simple as possible. OnInit, our component makes an async call to get some data for a dropdown list. When an item is selected in the list, data is async loaded for a second set of options. When one of these is selected, detail data is loaded async. When the data detail observable emits a new value for the detail object we set the components detail variable to the new data and the view's elements which are bound to this variable are updated.

The options described can either be manually selected one at a time, or passed in as route params. When supplied by the route params the selections are handled automatically, one after the other, triggering the same sequence of async calls ultimately resulting in the display of the detail data in the view.

Since rc1 we've been developing and building from Visual Studio with TypeScript 1.8 and using SystemJS for our runtime module loader. But we recently began preparing to ship and decided to try the angular-cli to bundle our scripts and build for production. The angular-cli build utilizes webpack for module loading.

Mostly, everything works ok with the cli build, but we are seeing a reduction of the amount of times change detection is invoked. In the situation I described above this has resulted in change detection ceasing to run about halfway through the series of async data calls which occurs when we load the app with our options passed in as route params. We can see in the console that the detail data has been returned, but the view is not updated after a certain point even though variables which are bound to template elements are in fact getting set to new values. The component's change detector doesn't run again to pick up the changes. If we click anywhere on the screen or do something else which triggers the change detector, the view immediately reflects the new data.

All versions of angular modules and other dependencies are up to latest stable versions and identical between both builds. But the Visual Studio/SystemJS build works (and seems to run change detection way more often based on debug statements from inside ngDoCheck) while the angular-cli/webpack version stalls on detecting changes about halfway through.

I've been stepping through the weeds of minified angular and zone scripts for about two days trying to see what's different, but so far haven't been able to discern why change detection is behaving so differently. I can get around the issue by using setTimeouts and other hacks in the component code to force the change detector to run, but I'd really like to understand why I should have to do this in the first place. Any insight anyone could offer would be greatly appreciated.

Additional info:

Here is the code in the component's subscription handler. I would think that just the fact that the sub received a new value would itself cause change detection, but since that isn't working I've also wrapped the handling of the new data in a setTimeout. In the method which handles the new data, variables which are bound to elements in the view are updated, which I would also think should trigger change detection, but no.

fundsService.fundDetail$.subscribe(
(updatedDetails) => {
    $log.debug('subscription received new data');
    $log.debug('setting timeout. will handle new data inside timeout');
    setTimeout(() => {
        this.handleNewFundDetail(updatedDetails);
        $log.debug('data set into vars bound to view. shouldn\'t change detection run now?');
    });
},
(error) => {
    $log.error('Error loading fund detail: ' + error.message);
    this.dialogService.error('Error loading fund detail.');
}

)

As you can see from the console log, the same code built in Visual Studio does in fact run change detection after this point. In fact it does so even without wrapping the data handling method in a timeout.

enter image description here

But when run from the angular cli build, there is no change detection after the new data is received in the subscription, no change detection resulting from the setTimeout, and no change detection when the template bound variables are changed.

enter image description here

So what exactly is happening here? Why isn't change detection firing?

UPDATE: If I nest a timeout inside another timeout in my subscription, change detection runs. If I just add a timeout after the handleNewDetail, or only wrap the timeout around the handleNewDetail, no change detection. But if I nest it like so, all of a sudden change detection occurs. Of course, doing something like this is ridiculous and arbitrary and should not be necessary and is not necessary until we build the app with angular-cli. :/

service.detail$.subscribe(
(updatedDetails) => {

    setTimeout(() => {
        this.handleNewDetail(updatedDetails);
        setTimeout(() => {
            $log.debug('inception timeout inside a timeout');
            //CHANGE DETECTION ACTUALLY HAPPENS
        },100);
    });
}    

Solution

  • Opened this as an issue with the angular-cli team and was asked to try removing the imports in polyfills.ts and instead pull down those scripts via CDN in the index.html like so

    <script src="https://unpkg.com/core-js/client/shim.min.js"></script>
    <script src="https://unpkg.com/zone.js@0.6.25?main=browser"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3"></script>
    

    This solved the issue with change detection, but I'm still not exactly sure why. Follow https://github.com/angular/angular-cli/issues/2752 for updates. Big thanks to @filipesilva from the angular team for the assistance.

    UPDATE:

    The better workaround to this issue which does not require pulling scripts down from the CDN or linking them separately inline in the index.html is to add them to the scripts array in angular-cli.json. This will do all the bundling, minification and tree-shaking on these files while still resolving the issue with change detection. The only caveat I'd add is to make sure that these are the first files in the array like so:

    "scripts": [
        "../node_modules/core-js/client/shim.min.js",
        "../node_modules/zone.js/dist/zone.js",
        "../node_modules/reflect-metadata/Reflect.js",
        "common/someOtherScript.js"
      ],