AngularJS ui.router:$state.go后无法捕获DOM的原因咨询
这个问题其实是AngularJS的digest循环和ui-router状态切换流程的典型时机问题,我来给你拆解清楚:
$state.go().then()的执行时机:
$state.go()返回的Promise在状态切换的逻辑层面完成时就会resolve——也就是路由的resolve函数执行完毕、目标状态的控制器已经初始化,但此时Angular还没有完成新视图的DOM渲染和挂载。因为Angular的DOM更新是在digest循环中批量处理的,这时候digest还没触发,<ui-view>里的新元素还没被插入到DOM树里,自然查不到。resolve和onSuccess的局限:resolve是在状态激活前执行的,本身就不可能拿到目标视图的DOM;而ui-router的
onSuccess(或者$stateChangeSuccess事件)也只是标志着状态切换的逻辑流程完成,DOM渲染还在等待下一次digest循环的触发,所以这时候用document.querySelector()依然找不到元素。setTimeout为什么能生效?:
setTimeout会把你的代码放到浏览器的宏任务队列里,等到当前的Angular digest循环完全结束、浏览器完成了DOM的更新和重绘之后才会执行。10ms的延迟其实是给了浏览器足够的时间完成DOM渲染,这时候再去查找元素自然就能找到了。不过这种方式不够可靠,因为不同设备的渲染速度不一样,延迟时间很难统一。
比起依赖setTimeout的“玄学延迟”,推荐用这些Angular原生的方式来确保DOM已就绪:
使用Angular的$timeout服务:
$timeout是Angular封装的定时器,它会在执行回调前自动触发一次digest循环,确保DOM已经完成更新。不用指定延迟时间,默认0ms就足够:$state.go('client').then(() => { $timeout(() => { uiService.showAdminPanel(); }); });利用指令的postLink函数:如果
showAdminPanel是针对client状态下的特定元素,最好把这个逻辑放到对应组件/指令的postLink阶段。postLink是Angular指令生命周期中,在当前元素及其所有子元素都渲染完成后才执行的阶段,这是Angular推荐的DOM操作时机。监听$viewContentLoaded事件:ui-router会在
<ui-view>的内容完全加载并渲染完成后触发$viewContentLoaded事件,你可以在client状态的控制器里监听这个事件:angular.module('app').controller('ClientController', function($scope, uiService) { $scope.$on('$viewContentLoaded', () => { uiService.showAdminPanel(); }); });
内容的提问来源于stack exchange,提问作者Michael Kutateladze

