angularjs随笔 -- 项目中使用 angularjs 踩过的坑

项目中使用 angularjs 踩过的坑

  • 1. 双向数据绑定不生效 ?
  • 2. 设置的定时器,清不掉!!!
  • 3. $on 事件多次触发 ?
  • 4. 总结

框架虽好用,但是也会有各种坑,本人使用了 angularjs 框架两年多的时间,碰到过很多的问题,以下针对典型问题做分析共享:

1. 双向数据绑定不生效 ?

问题背景:
我明明采用了 ng-model 进行了数据绑定,js 逻辑修改了数据,但是页面就是没有刷新,甚至于我在控制台进行断点调试,发现数据逻辑都没有问题,页面元素就是不刷新。

解决方法:

// 脏检查机制没有生效
if(!$scope.$$phase) {
	$scope.$apply();
}

再次刷新页面,发现双向数据绑定生效了!恭喜,问题解决…
但是,重点来了,以上方法可以解决 80% 遇到的不生效问题,我加了上面代码之后发现仍然没有生效,仔细检查代码逻辑,发现没有问题,傻眼了,抱着试试的态度:

// HTML 内容
<input type="text" ng-model="userText">
// controller 内容
$scope.userText = "文本内容";

修改绑定方式,将直接变量的形式,修改为以对象属性的形式绑定:

// HTML 内容
<input type="text" ng-model="obj.userText">
// controller 内容
$scope.obj = { 'userText' : "文本内容" };

刷新页面,发现双向数据绑定生效!

2. 设置的定时器,清不掉!!!

问题背景1:
我通过路由表配置了两个 view 页面(view1、view2):

myApp.config(['$routeProvider', function ($routeProvider) {

    $routeProvider.otherwise('/one');

    $routeProvider.when('/one',{
        templateUrl: './view1/view1.html',
        controller: 'oneCtrl'
    }).when('/two',{
        templateUrl: './view2/view2.html',
        controller: 'twoCtrl'
    })

}]);

在 view1 的 oneCtrl 中设置了定时器 $interval :

myApp.controller('oneCtrl', ['$scope', '$timeout', '$interval', function ($scope, $timeout, $interval) {

    $scope.timer = null;

    $timeout(function () {
    	// 定时器开启前一定要进行清除,定时器的使用都需要慎重考虑
        if ($scope.timer) {
            $interval.cancel($scope.timer);
            $scope.timer = null;
        }
        $scope.timer = $interval($scope.fn, 1000);
    });

    $scope.fn = function () {
        console.log(1);
    };
    
}]);

切换路由,切换到 two,也就是 view2 页面时,发现 view1 里面的定时器仍然在执行,what? 它竟然没有自动停止…
好吧,我太天真了 !

解决方法:
在 view1 的 oneCtrl 中监听 $destroy 事件,进行手动清理定时器:

// 当前 controller 中配置过定时器,那么需要在 destroy 的时候取消定时器
$scope.$on('$destroy', function () {
    console.log('销毁机制触发');
    $interval.cancel($scope.timer);
    $scope.timer = null;
});

问题解决,发现再次进行路由切换时,定时器停止与进行展示正常。

承上启下,我们设置页面跳转,配置HTML 和 controller 关联关系有两种方式,第一种,通过路由表进行关联,第二种,直接给 HTML 上添加 ng-controller=‘xxx’ 进行关联。

问题背景2:
项目中存在这种情况,我的 view2 页面中通过 include 引入了 view3 页面:

<div>
    view2


    <div ng-include="'./view3/view3.html'"></div>
</div>

view3 的 HTML 内容如下:

<div ng-controller="threeCtrl">
    {{ text }}
</div>

view3 的 controller 文件引入到 index.html 里面,view3 的 controller 也就是 threeCtrl 内容如下:

myApp.controller('threeCtrl', ['$scope', '$interval', function ($scope, $interval) {
    $scope.text = "view3 text";

    $scope.fn = function () {
        console.log(3);
    };

    $scope.timer = $interval($scope.fn, 1000);

    // 当前 controller 中配置过定时器,那么需要在 destroy 的时候取消定时器
    $scope.$on('$destroy', function () {
        console.log('view3 销毁机制触发');
        $interval.cancel($scope.timer);
        $scope.timer = null;
    });
}]);

可以看到我在 view3 中开启了定时器, 并且添加了 $destroy 监听清理定时器,view3 包含在 view2 里面,我们切换路由到 two,发现view3设置的定时器运行正常,切换路由到 view1 发现定时器仍然运行???

解决方法:
由此得出 view2 的HTML 销毁,view2 的 controller 销毁了,但是 view3 的 controller 没有触发 $destroy,在路由切换到非 view2 的时候,需要手动调用 view3 的 $destroy 方法:

<!DOCTYPE html>
<html lang="en" ng-app="myApp">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>angularjs basic knowledge</title>
</head>

<body ng-controller="myAppCtrl">

    <span ng-click="goView('one')">view1</span>
    <span ng-click="goView('two')">view2</span>

    <div ng-view></div>

    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
    <script src="http://cdn.bootcss.com/angular.js/1.5.8/angular-route.js"></script>


    <script>
        var myApp = angular.module('myApp', ['ngRoute']);
        myApp.controller('myAppCtrl', ['$scope', '$location', function ($scope, $location) {
            $scope.goView = function (val) {
                $location.path('/' + val);
                if ('two' != val) {
                    // twoCtrl 是通过 路由表 配置与HTML进行绑定的,切换路由时,angularjs会自己调用 $destroy
                    // threeCtrl 是通过 ng-controller 进行绑定的,没有经过路由表,在路由切换到 one 时,view3 的HTML隐藏了,但是需要手动调用 $destroy
                    var controller = document.querySelector('[ng-controller="threeCtrl"]');
                    var scope = angular.element(controller).scope();
                    scope.$destroy();
                }
            }
        }]);
    </script>

    <script src="./view1/view1Ctrl.js"></script>
    <script src="./view2/view2Ctrl.js"></script>
    <script src="./view3/view3Ctrl.js"></script>

    <script src="./router.js"></script>
</body>

</html>

问题解决,切换到 view1 时,view3 的定时器清除掉了!

3. $on 事件多次触发 ?

问题背景:
以上面的三个 view 视图前提,我添加一个事件触发:

<body ng-controller="myAppCtrl">

    <span ng-click="goView('one')">view1</span>
    <span ng-click="goView('two')">view2</span>
    <p ng-click="eventTrigger()">事件触发</p>

    <div ng-view></div>

    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
    <script src="http://cdn.bootcss.com/angular.js/1.5.8/angular-route.js"></script>


    <script>
        var myApp = angular.module('myApp', ['ngRoute']);
        myApp.controller('myAppCtrl', ['$scope', '$rootScope', '$location', function ($scope, $rootScope, $location) {
            $scope.goView = function (val) {
                $location.path('/' + val);
            }

            $scope.eventTrigger = function () {
            	// 只有采用 $rootscope 触发事件会引起 $on 多次触发
                $rootScope.$broadcast('testEvent');
            }

        }]);
    </script>
</body>

在 view1 里面添加 $on 事件接收

myApp.controller('oneCtrl', ['$scope', '$rootScope', '$timeout', '$interval', function ($scope, $rootScope, $timeout, $interval) {

    $rootScope.$on('testEvent', function () {
        console.log('view1Ctrl');
    });

    $scope.$on('$destroy', function () {
        console.log('销毁机制触发');
    });

}]);

多次进行路由切换view1、view2 视图后,点击 view1,然后点击事件触发按钮,触发 eventTrigger ,发现:view1 中 $on 事件触发了多次。

原因分析:
$on 的多次触发发生在 $rootScope 上,触发原因是由于多次的 HTML 隐藏显示,导致 $on 在根作用域上追加注册了多次,所以只要 $broadcast 触发,就会有多个 $on 执行,并且很难规避。

解决方法:
多次的路由切换或着显示隐藏,每次都伴随着 controller 中 $destroy 事件的触发,所以监听 $destroy 事件销毁时,释放当前的监听事件:

myApp.controller('oneCtrl', ['$scope', '$rootScope', '$timeout', '$interval', function ($scope, $rootScope, $timeout, $interval) {
	// 获取返回值
    var testEvent = $rootScope.$on('testEvent', function () {
        console.log('view1Ctrl');
    });

    $scope.$on('$destroy', function () {
        console.log('销毁机制触发');
        // 在页面隐藏时触发的 $destroy 中进行 调用即可
        testEvent();
        testEvent = null;
    });

}]);

经测试问题解决。

4. 总结

  1. 双向数据绑定机制不太完善,跟踪刷新出问题几率大,所以体现了单向数据绑定的优势;
  2. angularjs 生命周期的模糊不清导致项目出现各种怪异问题;
  3. 你都看到这了,麻烦点个赞呗,O(∩_∩)O哈哈~

你可能感兴趣的:(angularjs)