angularjs开发总结

环境搭建

  1. 基于node环境,需要npm工具,以及基于node环境的其他工具,如bower,webpack,grunt,gulp等
  2. 使用angular1.x开发SPA单页面程序
  3. 使用webpack去管理依赖,打包资源;html代码、css代码、js代码、图片等其他资源统一使用webpack打包,压缩,混淆。
  4. 使用WebStorm IDE进行开发,webstorm是一款非常优秀的前端开发IDE,在快捷键和代码提示上给予很大的帮助,特别是支持angular语法,es6语法,JSX语法等。

路由

目前SDK2.0用的是ngRoute ,论坛系统用的是ui.router。ui.router提供了比ngRoute 更强大的功能,比如多视图,嵌套视图。ui.router 是基于 state (状态)的, ngRoute 是基于 url 的,ui.router模块具有更强大的功能,主要体现在视图的嵌套方面。

在sdk2.0中页面需要加载国际化资源,需要加载JSBridgeLIB资源,这个时候在ui.router中就可以方便的使用路由配置中的resolve属性,或者使用抽象状态去等待资源的加载。

在论坛系统中会有tab页,每个tab页对应着数据的详细信息。而tab页在页面分享的时候是需要正确的定位的,故需要将tab页放置在url中,这个场景下用ui.router的父子试图也是比较合适的。

Controller间数据共享

  1. 通过parent scope作用域去共享数据,如: $rootScope ,兄弟controller可以通过父controller共享数据
  2. 通过事件广播去共享数据,$emit、$on、$broadcast
  3. 通过service去共享数据,由于service是单例的,所以可以用service去创建单实例的model对象来共享数据
  4. 通过directive指定属性进行数据绑定

ngIf / ngShow /ngHide区别

第一点区别是,ng-if 在后面表达式为 true 的时候才创建这个 dom 节点,ng-show 是初始时就创建了,用 display:block 和display:none 来控制显示和不显示。

第二点区别是,ng-if 会(隐式地)产生新作用域,ng-switch 、 ng-include 等会动态创建一块界面的也是如此。

这样会导致,在 ng-if 中用基本变量绑定 ng-model,并在外层 div 中把此 model 绑定给另一个显示区域,内层改变时,外层不会同步改变,因为此时已经是两个变量了。

在论坛系统中,遇到这个问题比较多。主要是在多tab页时,刚开始是通过ngif控制tab页的显示问题,就导致了tab页中的指令都会重新渲染,之前的状态就全没了。

定时器

定时器的使用$timeout $interval:由于页面路由跳转的时候可能定时器没有执行完成,故如果不主动清除定时器会造成系统的bug,需要我们主动清除定时器。

在路由变化事件中处理

$rootScope.$on('$locationChangeStart', function (event, url) {
    if (window.$timer) {
        try {
            $interval.cancel(window.$timer);
        } catch (err) {
            console.info(['路由变化时清除timer出错,请忽略', err]);
        }
    }
});

在controller的destory方法中处理

$scope.$on("$destroy", function() {
    if (window.$timer) {
        try {
            $interval.cancel(window.$timer);
        } catch (err) {
            console.info(['路由变化时清除timer出错,请忽略', err]);
        }
    }
});

XHR与跨域访问

跨域解决方式:JSONP 或 CORS
在CORS中需要一些配置

/* 设置cors post不发送options请求 */
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
/* 设置cors时发送浏览器cookie到服务器 需要服务器返回Header中加入Access-Control-Allow-Credentials: true */
$httpProvider.defaults.withCredentials = true;

在angular中可以通过拦截器去设置一些通用的东西,例
1.通过拦截器设置超时

/* 定义拦截器 */
.factory('timeoutHttpInterceptor', function () {
    return {
        request: function (config) {
            config.timeout = 1000 * 30;
            return config;
        }
    }
})
/* 设置超时 */
$httpProvider.interceptors.push('timeoutHttpInterceptor');

2.通过拦截器设置统一的异常处理

/* 定义拦截器 */
.factory('httpErrorInterceptor', ['$q', 'toast', function ($q, toast) {
    return {
        responseError: function (response) {
            switch (response.status) {
                case 200:
                    break;
                case -1:
                    toast('网络异常');
                    break;
                case 404:
                    toast('请求地址不存在(404)');
                    break;
                case 500:
                    toast('服务器开小差了,请稍后再试(500)');
                    break;
                case 502:
                    toast('Bad GateWay(502)');
                    break;
                default:
                    toast('网络异常(' + response.status + ')')
            }
            return $q.reject(response);
        }
    }
}])
/* 设置网络异常 */
$httpProvider.interceptors.push('httpErrorInterceptor');

3.通过拦截器设置那些XHR需要添加遮罩层

/* 定义拦截器 */
.factory('ajaxLoadingInterceptor', ["$q", "$rootScope", 'loading', function ($q, $rootScope, loading) {
    return {
        request: function (config) {
            config.mask === true && loading.show();
            return config || $q.when(config)
        },
        response: function (response) {
            response.config.mask === true && loading.hide();
            return response || $q.when(response);
        },
        responseError: function (response) {
            response.config.mask === true && loading.hide();
            return $q.reject(response);
        }
    };
}])
/* 设置XHR遮罩 */
$httpProvider.interceptors.push('ajaxLoadingInterceptor');

4.通过拦截器设置服务器返回未登录错误码是弹出登录框

/* 定义拦截器 */
.factory('loginInterceptor', function ($q, session, loginDialog) {
    return {
        response: function (response) {
            if (response.data && response.data.status == '1' && response.data.reset == 'b499') { //如果错误码是未登录
                loginDialog.show(); //弹出登录框
                session.removeAttribute('user');
                return $q.reject(response);
            } else {
                return response;
            }
        }
    }
})
/* 设置弹出登陆框 */
$httpProvider.interceptors.push('loginInterceptor');

指令

angular中的指令是个重要点,也是难点。指令绑定策略中的@(or @attr) =(or =attr) &(or &attr)的作用如下:

@(or @attr):使用@符号将本地作用域同DOM属性值进行绑定

=(or =attr):通过=可以将本地作用域同父级作用域上的属性进行双向数据绑定

&(or &attr):通过&符号可以对父级作用域进行绑定,以便在其中运行函数

下面以一个加载更多指令为例,展示@、=和&的用法

/* 声明指令 */
.directive('ourpalmLoadMore', function ($timeout, $rootScope) {
    return {
        restrict: 'E',
        replace: true,
        scope: {
            text: '@', //绑定属性-单向绑定 绑定文字:点击加载更多 | 加载中... | 没有更多了
            text2: '=', //绑定属性-双向绑定 绑定文字:点击加载更多 | 加载中... | 没有更多了
            loadMore: '&' //绑定事件
        },
        template: '<div class="arrow_down_on" ng-disabled="isDisabled"><div ng-click="onClick();"><a><em></em>{{text || "点击加载更多"}}</a></div></div>',
        link: function (scope, elem, attrs) {
            var method = scope.loadMore; //获取绑定的事件
            scope.isDisabled = false;
            scope.onClick = function () {
                if (scope.isDisabled) {
                    return;
                }
                scope.isDisabled = true;
                scope.$eval(method); //调用绑定的事件
            };
        }
    }
})
/* 使用指令 注意loadText的两种使用方式,对应上面的@和=号 */
<ourpalm-load-more load-more="loadMore();" text="{{loadText}}" text2="loadText"></ourpalm-load-more>

webpack

webpack的功能十分的强大,基本上可以替代grunt和gulp。这里不讲解webpack的用法,主要说下我们用到的webpack的功能。

1.打包js
2.打包css、svg、ttf、woff、eot
3.打包html
4.打包图片
5.处理css兼容浏览器前缀,autoprefixer
6.处理angular打包压缩后导致的依赖注入不可用问题 ng-annotate-webpack-plugin
7.转译es6语法为es5语法,babel
8.打包压缩混淆
9.其他可根据需求添加

开发中的实际问题

1.移动端键盘遮盖输入框的问题


8572B4AA56034E4F72B5D8EC4404B1E1.png

这个问题的解决方式是弹出一个置顶的输入框,在置顶的输入框中输入,在focusout事件中将数据关联到实际的输入框中。
angular指令<ourpalm-focus></ourpalm-focus>的部分实现为:

directive('ourpalmFocusText', function ($timeout) {
    return {
        replace: true,
        restrict: 'A',
        require: '?ngModel', //通过ngModel引用目标输入框
        link: function ($scope, $ele, $attrs, ngModel) {
            $ele.attr('readonly', true);
            $ele.on('click', function () {
                $scope.$emit('ourpalm-focus:open', 'text', $ele, function (newVal) {
                    if (ngModel) {
                        $timeout(function () {
                            ngModel.$setViewValue(newVal); //在置顶输入框失去焦点时更新目标输入框
                        });
                    }
                    $($ele).val(newVal);
                })
            })
        }
    }
})

2.history.back()或者history.go(-1);在ios7设备webview中不可用的问题。

问题描述:angular单页面程序,为了控制用户流量,启用h5 appcache,页面使用hash路由,在appcache启用的情况下,ios7 webview在history.back()上有bug。

解决思路:a.可以通过特殊机型判断,ios7不使用appcache,其他机型使用appcache;b.既然是单页面程序,自己可以实现一个基于内存的history api,不用浏览器默认的history api。

目前sdk2.0使用的是自己实现的基于内存的history api,实现原理:监听window.onhashchange事件。

3.移动端横屏显示问题


436E2EBFB6F7FD503A9FF47C6A8066BC.png

通过一个动画层提示用户切换到竖屏查看,不让用户查看。

在实现细节上有一个要注意的是,本质上是一个比较简单的问题,css3媒体查询+css3动画就可以搞定了。但是css3媒体查询在android手机多数浏览器的支持上有bug,注意是bug,不是不支持css3媒体查询。表现是在键盘弹出的时候,android媒体查询会把竖屏当做横屏处理。

下面是通过js实现代替媒体查询实现:

.directive('ourpalmLandscape', function ($timeout) {
    return {
        restrict: 'AE',
        replace: true,
        template: `
            <div class="ourpalm-landscape" ng-show="landscape">
                <img src="" alt="">
                <p>
                    为了更好的体验,请使用竖屏浏览
                </p>
            </div>
        `,
        link: function (scope, ele, attrs) {
            refresh();
            window.addEventListener('orientationchange', function (event) { //通过orientationchange事件代替css3的媒体查询
                refresh();
            });
            function refresh() {
                if (window.orientation == 180 || window.orientation == 0) {
                    $timeout(function () {
                        scope.landscape = false;
                    });
                }
                if (window.orientation == 90 || window.orientation == -90) {
                    $timeout(function () {
                        scope.landscape = true;
                    });
                }
            }
        }
    }
})

4.webpack --watch监听文件变化自动打包不好使,修改webstrom的配合 取消 "safe write"


clipboard1.png

5.angular页面列表页与详情页切换导致数据需要重新加载和滚动条无法回位问题

clipboard.png

通过上面Controller间数据共享的介绍我们已经知道了怎么样去保存数据,这里数据的保存通过Service单例去保存,原因是论坛系统中这样的页面太多了,如果通过ui.router的嵌套模板去定义父子关系的话,论坛系统中就会有很多额外的父子关系,以及父子试图。所以这里采用单例的方式去保存数据来达到页面切换时数据不丢失的目的。
另外的一个问题就是除了保存数据在页面返回的时候滚动条需要滚到历史的位置,这里我们通过一个自定义的模块来实现,如下:

(function (angular) {
    angular
        .module('ourpalm-util-scrolltop', [])
        .run(function ($rootScope, $state) {
            /* 监听ui.router状态改变事件,记录滚动条的位置 */
            $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams, options) {
                if (fromState.scrollTop === true && window.localStorage) {
                    var scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
                    window.localStorage.setItem(fromState.name, scrollTop);
                }
            });
            /* 在页面加载完成的时候将滚动条滚动到上次的位置 */
            $rootScope.$on('$viewContentLoaded', function (event) {
                if ($state.current.scrollTop === true && window.localStorage) {
                    var scrollTop = window.localStorage.getItem($state.current.name);
                    if (scrollTop) {
                        window.setTimeout(function () {
                            window.scrollTo(0, scrollTop);
                        }, 10)
                    }
                }
            });
        })
})(angular);

6.angular中定位到指定的tab页与数据保存


clipboard2.png

这个场景涉及到以下问题:

  1. 页面加载了很多页,返回时需要保存之前的数据
  2. 返回的时候需要定位到正确的tab页
  3. 返回的时候滚动条需要滚动到正确的位置
  4. 浏览器地址分享出去后,其他用户打开网址可以定位到正确的tab页
    解决思路:

数据保存和滚动条回位的问题可以通过上面的方式解决。浏览器地址的问题,可以将tab页对应的id放到浏览器hash路由中,这里通过ui.router的父子模板来实现,定义如下:

.state('base.board', { //父state -- tab页 标题
    url: '/board/:sectionid',
    template: require('../ourpalm-view/board_main.html')
})
.state('base.board.tab', {//子state -- tab页 内容
    url: '/:subSectionId',
    template: require('../ourpalm-view/board_main_tab.html')
})

7.android关闭硬件加速会出现兼容性问题,如部分低端机造成css3属性不支持


clipboard5.jpeg
android:layerType="software"  存在兼容性问题
android:layerType="hardware"  正常

8.android和ios的webview js bridge开源实现
android的:https://github.com/lzyzsd/JsBridge
ios的:https://github.com/marcuswestin/WebViewJavascriptBridge

9.手机端点击事件会有300ms的延迟问题
fastclick about the 300ms delay; 详见 https://docs.angularjs.org/api/ngTouch

10.fiddle使用
a.设置断点
在命令行中输入 bpafter sdk2.appcache 回车
b.清除断点
在命令行中输入 bpafter 回车

angular的学习文章

  1. https://toddmotto.com/directive-to-directive-communication-with-require/
  2. https://courses.toddmotto.com/?utm_source=toddmotto.com/one-way-data-binding-in-angular-1-5/
  3. https://thinkster.io/angularjs-es6-tutorial
  4. https://thinkster.io/topics/angular
  5. https://github.com/angular-ui/ui-router
  6. https://www.ng-book.com/media/ng2/ng-book-2-table-of-contents.pdf
  7. https://github.com/toddmotto/angular-styleguide#components
  8. https://docs.angularjs.org/guide/component
  9. https://ui-router.github.io/guide/ng1/route-to-component
  10. https://angular.cn/
  11. https://angular.cn/resources/

在angular中指令是一个非常强大和非常难的知识点,关于angular1.x和angular2.x中的指令和组件可以参考一个通用的ui组件库源码来学习。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容