【问题标题】:Variable change is not visible in my $scope function变量更改在我的 $scope 函数中不可见
【发布时间】:2014-08-01 08:33:50
【问题描述】:

当状态更改事件发生时,即使我在事件侦听器中看到变量已更新,我也有一个变量未更新为 $scope 函数的问题。

代码如下:

angular.module('testapp')
.controller('AnotherCtrl',
['$scope', '$rootScope', '$state',
    function ($scope, $rootScope, $state) {
        'use strict';
        console.log("Init prevState.");
        var prevState = 'null_state';
        $scope.state = prevState;


        $rootScope.$on('$stateChangeError',
            function (event, toState, toParams, fromState, fromParams, error) {
                console.log("Error");
                if (toState.name === 'anotherState') {

                    console.log("Old State:" + prevState);
                    prevState = fromState.name;
                    console.log("New State:" + prevState);


                }
            })

        $rootScope.$on('$stateChangeSuccess',
            function (event, toState, toParams, fromState, fromParams) {
                console.log("Success");
                if (toState.name === 'anotherState') {

                    console.log("Old State:" + prevState);
                    prevState = fromState.name;
                    console.log("New State:" + prevState);

                }
            })


        $scope.goBack = function () {
            //$scope.state = 'anotherState';
            console.log("goBack:" + prevState);
            $state.transitionTo(prevState, {arg: 'Robert'});
        };
    }]);

这是 HTML 模板:

<div>
<h1>Another view</h1>
<br>
<br>
State:  <span ng-model="state">{{state}}</span>
<br>
<br>
<button ng-click="goBack()">Back</button>
</div>

这是控制台输出:

所以当我按下调用函数goBack() 的按钮时,prevState 变量仍然是'null_state'。

谁能解释一下问题出在哪里?

更新在查看答案后 我已经审查并测试了所有建议的解决方案。核心问题 AFAICS 与不可变的字符串类型无关。正如我所见,最好的答案来自@Khanh TO。最初创建控制器时未初始化事件侦听器。这是通过引导(angular.module.run)修复的。此外,每次加载状态/视图时都会重新初始化控制器,从而导致变量 prevState 被重新初始化,因此侦听器函数和 goBack() 函数使用不同的外部 prevState 变量。将 prevState 存储在 rootScope 上解决了这个问题。

【问题讨论】:

  • 可以分享一下这个控制器相关的HTML吗?

标签: javascript angularjs


【解决方案1】:

也许你想改变:

var prevState = 'null_state';

到

$scope.prevState = 'null_state';

并相应地修复其他问题。

【讨论】:

    【解决方案2】:

    每次更改路由时,当前控制器被销毁(广播 $destroy 事件)并创建一个新控制器。

    您可以在控制器内部看到控制器何时被销毁:

    $scope.$on('$destroy',function () {
       console.log("The controller is destroyed.");
    });
    

    在应用程序中维护数据的方式是通过Services(如$state)或通过$rootScope。

    这意味着当您创建控制器时,您可以从服务中获取数据。为此创建一个 getter 函数。

    另一方面,如果要将数据与控制器绑定,则必须更新 $scope.state,而不是 prevState。该模板仅更新 $scope 中的值。

    结果:

    angular.module('testapp')
    .controller('AnotherCtrl',
    ['$scope', '$rootScope', '$state',
        function ($scope, $rootScope, $state) {
            'use strict';
            console.log("Init prevState.");
            $scope.state = $state.getState(); //create the getter
    
    
            $rootScope.$on('$stateChangeError',
                function (event, toState, toParams, fromState, fromParams, error) {
                    console.log("Error");
                    if (toState.name === 'anotherState') {
    
                        console.log("Old State:" + $scope.state);
                        $scope.state = fromState.name;
                        console.log("New State:" + $scope.state);
    
    
                    }
                })
    
            $rootScope.$on('$stateChangeSuccess',
                function (event, toState, toParams, fromState, fromParams) {
                    console.log("Success");
                    if (toState.name === 'anotherState') {
    
                        console.log("Old State:" + $scope.state);
                        $scope.state = fromState.name;
                        console.log("New State:" + $scope.state);
    
                    }
                })
    
    
            $scope.goBack = function () {
                //$scope.state = 'anotherState';
                console.log("goBack:" + $scope.state);
                $state.transitionTo($scope.state, {arg: 'Robert'});
            };
        }]);
    

    【讨论】:

      【解决方案3】:

      您面临的问题在于 JavaScript 闭包的工作方式。当您在函数 goBack() 和与 stateChangeSuccess 关联的函数的范围内访问 prevState 时,您将保留对父级 prevState 的句柄。但是 JavaScript 中的字符串是不可变的。因此,当在 goBack 中为 prevState 赋值时,会创建一个新字符串,并且 goBack() 中的 prevState 指向该字符串。但是与 stateChangeSuccess 关联的 prevState 仍然指向“null_state”。

      你可以做的就是将 prevState 包装在一个对象中:

      var prevData = {prevState: 'null_state'};
      

      并使用prevData.prevState 修改prevState。这将起作用的原因是因为所有闭包都持有对同一对象的引用(因为现在您使用的是对象而不是字符串),因此对字符串的更改将传播到不同的函数。

      有关详细信息,请仔细阅读以下内容:

      How do JavaScript closures work?

      Pass a string by reference in Javascript

      【讨论】:

        【解决方案4】:

        根本原因是每当您更改状态时,角度重新初始化与当前状态关联的控制器,导致您之前存储的状态被清除并重新- 初始化。

        因此,解决方案是将之前的状态存储在共享服务中。在这种情况下,我们可以只使用$rootScope。

        $rootScope.$on('$stateChangeError',
                    function (event, toState, toParams, fromState, fromParams, error) {
                        console.log("Error");
                        if (toState.name === 'anotherState') {
                            $rootScope.prevState = fromState.name;
                        }
         });
        
         $rootScope.$on('$stateChangeSuccess',
                    function (event, toState, toParams, fromState, fromParams) {
                        console.log("Success");
                        if (toState.name === 'anotherState') {
                            $rootScope.prevState = fromState.name;
                        }
          });
        
          $scope.goBack = function () {
            $state.transitionTo($rootScope.prevState, {arg: 'Robert'});
            //or $state.transitionTo($scope.prevState, {arg: 'Robert'}); //due to scope inheritance
          };
        

        但是,每当AnotherCtrl重新初始化时,您的代码就会出现一个严重的问题,即向$rootScope 注册越来越多的事件处理程序。您的代码的第二个问题是第一次加载页面时$stateChangeSuccess 事件不会触发https://github.com/angular-ui/ui-router/issues/299

        您应该只在.run 块中注册一次事件处理程序:

        angular.module('testapp')
               .run(["$rootScope",function ($rootScope){
                        $rootScope.$on('$stateChangeError',
                           function (event, toState, toParams, fromState, fromParams, error) {
                              console.log("Error");
                              if (toState.name === 'anotherState') {
                                $rootScope.prevState = fromState.name;
                              }
                           });
        
                       $rootScope.$on('$stateChangeSuccess',
                          function (event, toState, toParams, fromState, fromParams) {
                            console.log("Success");
                            if (toState.name === 'anotherState') {
                                $rootScope.prevState = fromState.name;
                            }
                        });  
                    }]);
        

        只有这段代码应该保留在您的控制器中:

        $scope.goBack = function () {
            $state.transitionTo($rootScope.prevState, {arg: 'Robert'});
            //or $state.transitionTo($scope.prevState, {arg: 'Robert'}); //due to scope inheritance
         };
        

        【讨论】:

        • 我也是这么认为的,但是如果重新初始化控制器,“Init prevState”怎么来的。控制台打印没有显示在他的控制台屏幕截图上?这是控制器做的第一件事。
        • @haimlit:控制台中有"Init prevState."。请仔细看看。
        • 但如果你说的是正确的,应该有两个 - 一个在初始化控制器时,另一个在由于状态变化而重新初始化时。
        • @haimlit:看代码,我们可以看出他来自"modeltest.transfer",应该只有一个 "Init prevState."。我正在使用 Angular 1.2.17,流程是这样的:$stateChangeSuccess 在重新初始化控制器之前首先被触发(在控制台中记录 "Init prevState.")。我也觉得控制台有问题,因为"Init prevState." 应该在"New State" 和"go back" 之间。不确定在其他版本中,控制器初始化在$stateChangeSuccess之前运行
        • @haimlit:我不确定他是如何重现这个案例的。这可能与我上面指出的问题有关。代码第一次不起作用,每当我们进入这种状态时,事件处理程序就会越来越多地注册。
        猜你喜欢
        • 1970-01-01
        • 2020-04-29
        • 1970-01-01
        • 2017-11-10
        • 1970-01-01
        • 1970-01-01
        • 2016-03-07
        • 2019-05-22
        • 1970-01-01
        相关资源
        最近更新 更多