Angular $rootScope:inprog 问题探究

码影探路者
• 阅读 17165

TL;DR

这是一个关于 $rootScope:inprog 错误在什么样的情况下被触发,和如何解决的故事。

场景和问题

这几天在写一个 service 。这个 service 中有个状态需要注入到 directive 中做页面展现。因为状态的改变由另一个插件控制,不在 Angular 的 event loop 中。为了触发 dirty-checking 我在 service 中调用了 $rootScope.$digest()

service 代码大概如下所示:

const STATUS = {
  A: 'A',
  B: 'B',
}

class SomeService {
  constructor($rootScope) {
    this.$rootScope = $rootScope
  }

  start() {
    this.plugin = initPlugin

    // Register plugin callbacks
    this.plugin.onStateA = () => { this._setStatus(STATUS.A) }
    this.plugin.onStateB = () => { this._setStatus(STATUS.B) }
  }

  _setStatus(status) {
    this.status = status
    this.$rootScope.$digest()
  }
}

angular.module('app.someMod').service('someService', SomeService)

目前为止一切正常,直到因为需求改动,需要加一个状态,这个状态的改变是通过 directive 中的按钮触发的,于是我在 service 中加了一个方法,在 directive 中调用,代码如下:

// In service
class someService {
  connect() {
    this._setStatus(STATUS.C)
  }
}

// In directive, the "btnClick" is bound to an element's ng-click
scope.btnClick = () => {
  someService.connect()
}

然后一点击按钮,程序就跪了…… 控制台中报的错误是 $rootScope:inprog

解决方法

这段错误的官方描述如下:

At any point in time there can be only one $digest or $apply operation in progress. This is to prevent very hard to detect bugs from entering your application. The stack trace of this error allows you to trace the origin of the currently executing $apply or $digest call, which caused the error.

简单来说,$digest$apply 是用来触发 dirty-checking 的方法。前者强制触发一次 dirty-checking ,后者让一段代码执行完成后触发 dirty-checking 。但是 Angular 一次只允许一个 $digest 或者 $apply 运行。上面例子里的代码会挂,是因为 scope.btnClick 本身已经在 $apply 中执行了,但 someService.connect 内部通过 _setStatus 又调用了一次 $digest ,这就触发了两次。

这让我反思为什么要手动调用 $digest ?其实我的目的只是确保所有状态改变都触发 dirty-checking 。因为这个 service 中哪些代码不会触发 dirty-checking 是很明确的,那就是插件回调。所以直接在回调中使用 $apply 就可以解决问题。

修改后的代码如下:

// In service
start() {
  // Wrap code in $apply
  this.plugin.onStateA = () => { this._wrapStatusChange(STATUS.A) }
  this.plugin.onStateB = () => { this._wrapStatusChange(STATUS.B) }
}

connect() {
  // Change status directly
  this.status = STATUS.C
}

_wrapStatusChange(status) {
  this.$rootScope.$apply(() => {
    this.status = status
  })
}

总结

只在必要的时候使用 $apply 处理那些不会触发 dirty-checking 的代码。大部分的时候 $digest 都可以被 $apply 取代。

参考资料

$rootScope:inprog
Angular 对异常的描述。这种异常附带在线文档的方式还是很方便的。顺带一提 React 的异常信息也是这样。

$rootScope.Scope
Scope 的 API ,里面可以查到 $digest 和 $apply 的详细解释。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Karen110 Karen110
3年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
桃浪十七丶 桃浪十七丶
4年前
SSM项目中遇到Could not autowire. No beans of ‘XXX‘ type found.错误
写代码时候遇到了这个问题Couldnotautowire.NobeansofHeadLineService'typefound.提示这个错误,是因为在Service的实现类HeadLineServiceImpl中忘记在类的头部加注解@Service解决如下图。
Stella981 Stella981
3年前
Javascript判断Video视频播放、暂停、结束完成及获取长度事件监听处理
在日常应用场景中,可能会遇到这么一个情况,需要判断用户是否完整的观看完了一部视频,在这个场景中,和视频相关的事件大体涉及到几个部分,获取视频长度,视频开始播放,暂停播放和播放结束,下面来看下如何通过JavaScript来监听获取视频的这几种状态。html页面视频标签大体如下:<video id\"video" controls\"controls
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Stella981 Stella981
3年前
PowerDesigner列名、注释内容互换
在用PowerDesigner时,常常在NAME或Comment中写中文在Code中写英文,Name只会显示给我们看,Code会使用在代码中,但Comment中的文字会保存到数据库TABLE的Description中,有时候我们写好了Name再写一次Comment很麻烦,以下两段代码就可以解决这个问题。在PowerDesigner中PowerDesig
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
码影探路者
码影探路者
Lv1
江山代有才人出,各领风骚数百年。
文章
4
粉丝
0
获赞
0