angular 2 变化检测与 cli 和 webpack 的行为不同
angular 2 change detection behaves differently with cli & webpack
我们的应用程序遇到了一个问题,这个问题很难确定。在这一点上,我比较确信 angular(或区域,或 webpack,或 angular-cli)中某处存在错误,但重现所需的应用程序的复杂性使其成为有点禁止 post 作为错误报告或 plunk。所以我想我 post 在这里希望有人可以提供一些处理过类似挑战的见解。
我们正在加载的页面包含多个相互交互的组件,但我会尽量使问题的描述尽可能简单。 OnInit,我们的组件进行异步调用以获取下拉列表的一些数据。在列表中选择一个项目时,将异步加载第二组选项的数据。选择其中之一时,详细数据将异步加载。当 data detail observable 为 detail 对象发出新值时,我们将 components detail 变量设置为新数据,并更新绑定到该变量的视图元素。
描述的选项可以一次手动选择一个,也可以作为路由参数传递。当由路由参数提供时,选择会自动处理,一个接一个地触发相同的异步调用序列,最终导致在视图中显示详细数据。
自 rc1 以来,我们一直在使用 TypeScript 1.8 从 Visual Studio 开发和构建,并将 SystemJS 用于我们的 运行time 模块加载器。但是我们最近开始准备发布并决定尝试使用 angular-cli 来捆绑我们的脚本并构建生产环境。 angular-cli 构建利用 webpack 进行模块加载。
大多数情况下,cli 构建一切正常,但我们看到调用更改检测的次数减少了。在我上面描述的情况下,这导致更改检测停止到 运行 大约在一系列异步数据调用的一半,当我们使用作为路由参数传递的选项加载应用程序时发生。我们可以在控制台中看到详细数据已经返回,但是在某个点之后视图没有更新,即使绑定到模板元素的变量实际上被设置为新值。组件的更改检测器不会再次 运行 来获取更改。如果我们单击屏幕上的任意位置或执行其他触发更改检测器的操作,视图会立即反映新数据。
angular 模块和其他依赖项的所有版本都是最新的稳定版本,并且两个版本之间是相同的。但是 Visual Studio/SystemJS 构建有效(并且似乎 运行 更频繁地根据来自 ngDoCheck 内部的调试语句更改检测方式)而 angular-cli/webpack 版本在检测到一半的变化时停滞不前。
两天来我一直在研究缩小的 angular 和区域脚本,试图看看有什么不同,但到目前为止还无法辨别为什么变化检测的行为如此不同.我可以通过在组件代码中使用 setTimeouts 和其他 hack 来强制更改检测器为 运行 来解决这个问题,但我真的很想了解为什么我必须首先这样做。任何人都可以提供任何见解,我们将不胜感激。
附加信息:
这是组件订阅处理程序中的代码。我认为子接收到新值这一事实本身会导致更改检测,但由于这不起作用,我还将新数据的处理包装在 setTimeout 中。在处理新数据的方法中,绑定到视图中元素的变量被更新,我也认为这应该触发变化检测,但没有。
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.');
}
)
正如您从控制台日志中看到的那样,在 Visual Studio 中构建的相同代码实际上会 运行 在这一点之后进行更改检测。事实上,即使没有将数据处理方法包装在超时中,它也会这样做。
但是当 运行 从 angular cli 构建时,在订阅中接收到新数据后没有变化检测,setTimeout 没有变化检测,并且没有变化检测模板绑定变量已更改。
那么这里到底发生了什么?为什么不触发更改检测?
更新:如果我在我的订阅中将一个超时嵌套在另一个超时中,更改检测 运行s。如果我 只是 在 handleNewDetail 之后添加超时,或者 only 将超时包裹在 handleNewDetail 周围,则不会进行更改检测。但是如果我像这样嵌套它,就会突然发生变化检测。当然,做这样的事情是荒谬和任意的,不应该是必要的,在我们使用 angular-cli 构建应用程序之前也不是必需的。 :/
service.detail$.subscribe(
(updatedDetails) => {
setTimeout(() => {
this.handleNewDetail(updatedDetails);
setTimeout(() => {
$log.debug('inception timeout inside a timeout');
//CHANGE DETECTION ACTUALLY HAPPENS
},100);
});
}
将此作为 angular-cli 团队的问题打开,并被要求尝试删除 polyfills.ts 中的导入,而是通过 index.html 中的 CDN 拉下这些脚本,例如所以
<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>
这解决了变化检测的问题,但我仍然不确定原因。关注 angular 团队的 https://github.com/angular/angular-cli/issues/2752 for updates. Big thanks to @filipesilva 以获得帮助。
更新:
这个问题的更好解决方法是将它们添加到 angular-cli.json。这将对这些文件进行所有的捆绑、缩小和摇树优化,同时仍然通过更改检测解决问题。我要添加的唯一警告是确保这些是数组中的第一个文件,如下所示:
"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"
],
我们的应用程序遇到了一个问题,这个问题很难确定。在这一点上,我比较确信 angular(或区域,或 webpack,或 angular-cli)中某处存在错误,但重现所需的应用程序的复杂性使其成为有点禁止 post 作为错误报告或 plunk。所以我想我 post 在这里希望有人可以提供一些处理过类似挑战的见解。
我们正在加载的页面包含多个相互交互的组件,但我会尽量使问题的描述尽可能简单。 OnInit,我们的组件进行异步调用以获取下拉列表的一些数据。在列表中选择一个项目时,将异步加载第二组选项的数据。选择其中之一时,详细数据将异步加载。当 data detail observable 为 detail 对象发出新值时,我们将 components detail 变量设置为新数据,并更新绑定到该变量的视图元素。
描述的选项可以一次手动选择一个,也可以作为路由参数传递。当由路由参数提供时,选择会自动处理,一个接一个地触发相同的异步调用序列,最终导致在视图中显示详细数据。
自 rc1 以来,我们一直在使用 TypeScript 1.8 从 Visual Studio 开发和构建,并将 SystemJS 用于我们的 运行time 模块加载器。但是我们最近开始准备发布并决定尝试使用 angular-cli 来捆绑我们的脚本并构建生产环境。 angular-cli 构建利用 webpack 进行模块加载。
大多数情况下,cli 构建一切正常,但我们看到调用更改检测的次数减少了。在我上面描述的情况下,这导致更改检测停止到 运行 大约在一系列异步数据调用的一半,当我们使用作为路由参数传递的选项加载应用程序时发生。我们可以在控制台中看到详细数据已经返回,但是在某个点之后视图没有更新,即使绑定到模板元素的变量实际上被设置为新值。组件的更改检测器不会再次 运行 来获取更改。如果我们单击屏幕上的任意位置或执行其他触发更改检测器的操作,视图会立即反映新数据。
angular 模块和其他依赖项的所有版本都是最新的稳定版本,并且两个版本之间是相同的。但是 Visual Studio/SystemJS 构建有效(并且似乎 运行 更频繁地根据来自 ngDoCheck 内部的调试语句更改检测方式)而 angular-cli/webpack 版本在检测到一半的变化时停滞不前。
两天来我一直在研究缩小的 angular 和区域脚本,试图看看有什么不同,但到目前为止还无法辨别为什么变化检测的行为如此不同.我可以通过在组件代码中使用 setTimeouts 和其他 hack 来强制更改检测器为 运行 来解决这个问题,但我真的很想了解为什么我必须首先这样做。任何人都可以提供任何见解,我们将不胜感激。
附加信息:
这是组件订阅处理程序中的代码。我认为子接收到新值这一事实本身会导致更改检测,但由于这不起作用,我还将新数据的处理包装在 setTimeout 中。在处理新数据的方法中,绑定到视图中元素的变量被更新,我也认为这应该触发变化检测,但没有。
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.');
}
)
正如您从控制台日志中看到的那样,在 Visual Studio 中构建的相同代码实际上会 运行 在这一点之后进行更改检测。事实上,即使没有将数据处理方法包装在超时中,它也会这样做。
但是当 运行 从 angular cli 构建时,在订阅中接收到新数据后没有变化检测,setTimeout 没有变化检测,并且没有变化检测模板绑定变量已更改。
那么这里到底发生了什么?为什么不触发更改检测?
更新:如果我在我的订阅中将一个超时嵌套在另一个超时中,更改检测 运行s。如果我 只是 在 handleNewDetail 之后添加超时,或者 only 将超时包裹在 handleNewDetail 周围,则不会进行更改检测。但是如果我像这样嵌套它,就会突然发生变化检测。当然,做这样的事情是荒谬和任意的,不应该是必要的,在我们使用 angular-cli 构建应用程序之前也不是必需的。 :/
service.detail$.subscribe(
(updatedDetails) => {
setTimeout(() => {
this.handleNewDetail(updatedDetails);
setTimeout(() => {
$log.debug('inception timeout inside a timeout');
//CHANGE DETECTION ACTUALLY HAPPENS
},100);
});
}
将此作为 angular-cli 团队的问题打开,并被要求尝试删除 polyfills.ts 中的导入,而是通过 index.html 中的 CDN 拉下这些脚本,例如所以
<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>
这解决了变化检测的问题,但我仍然不确定原因。关注 angular 团队的 https://github.com/angular/angular-cli/issues/2752 for updates. Big thanks to @filipesilva 以获得帮助。
更新:
这个问题的更好解决方法是将它们添加到 angular-cli.json。这将对这些文件进行所有的捆绑、缩小和摇树优化,同时仍然通过更改检测解决问题。我要添加的唯一警告是确保这些是数组中的第一个文件,如下所示:
"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"
],