angular体验

上次学着用react写了一个url收藏简单功能。这次照着这个需求,用angular1来实践一下。

先列一下需求:

react基本dom都在jsx代码体现不同,angular要结合模板和js

可以先定义好基本dom:

<div class="url-box" ng-controller="ubCtrl">
                <div class="add-form">
                <div class="form-line">
                    <label for="url_input">URL&ensp;:</label>
                    <input type="text" id="url_input"  placeholder="请输入url">
                </div>
                <div class="form-line">
                    <label for="desc_input">描述:</label>
                    <input type="text" id="desc_input"  placeholder="请输入描述">
                </div>
                <div class="form-line">
                    <div class="styled-select blue semi-square">
                        <select id="type_input" >
                        </select>
                    </div>
                </div>
                <div class="form-line" >
                    <label for="new_type_input">类型:</label>
                    <input type="text" id="new_type_input"  placeholder="请输入新类型">
                </div>
                <button class="btn" >提交</button>
            </div>
            <div class="all-list">
                <div class="list-type">
                    <a class="type-item" ></a>
                </div>
                <div class="list">
                    <p class="item" >
                        <a href=""></a>
                        <a class="close">删除</a>
                    </p>
                </div>
            </div>
        </div>

忘了,没说明一下项目结构:但其实也很简单

-lib angular.js

-js index.js

-index.html

angular1用的js既非es6,也不需要jsx编译,所以页面直接引用就好了。

页面用到的数据方面,我沿用react实践时的数据

angular的概念了,基本是module跟control,考虑到表单跟列表直接有交互,而多个control之间交互可能比较复杂,于是我采取一个control来实现,事实上内容也很简单。

整个部分里面,应当数链接类型列表最为简单,先看它怎么实现的:

首先是模板,应该是接受一个变量数组的数据,然后对照到模板,遍历输出对应的dom,其次是点击每个类型,要有一个触发url列表数据的变动展示(这个等说到列表的时候再一并说明)

说到遍历数组,首先想到的应该是ngRepeat指令了,其语法如下:

<div ng-repeat="item in list"></div>

基本可以从代码语义上分析出,这个模板会根据变量list输出所有的div来。

那模仿一下这次实现类型列表的模板如下:

<div class="list-type">
    <a class="type-item" ng-repeat="type in types" ng-class="{active:currentType==type}"  ng-click="showList(type)"></a>
</div>

假设,我们在js那边设定好了$scope的types变量存储的是url的类型数组,那这里就是类似于:

for(var i=0;i<types.length;i++){
    //伪代码
    <a class="xxx">types[i]</a>
}

再者,我们看到这里有个ngClass指令,这个指令的用法是设定一个根据判断来决定是否添加给当前domclass 比如这里的代码,意为根据currentType==type来决定当前dom是否要设置active这个class,其实就是给展示当前选中的类型。

ngClick指令,则是设置了当前dom的一个点击事件触发,当dom被点击时,会调用$scope.showList(type); 这里的type不再需要加``;

我们再看一下js那边怎么写对应的逻辑:

var app = angular.module('ubApp', []);

app.controller('ubCtrl', function($scope){
//刷新类型列表数据和当前选中类型
    var refreshTypes = function(){
        $scope.types = gData.getTypeList();
        $scope.types2Select = $scope.types.map(function(e,i){
            var typeObj = {};
            typeObj['label'] = e;
            typeObj['key'] = e;
            return typeObj;
        }).concat({
            label:'新增类型',
            key:'add-new'
        });
        $scope.currentType = '';
    };

    $scope.showList = function(type){
        $scope.currentType = type;
        $scope.list = gData.getByType(type);
        if($scope.list.length==0){
            refreshTypes();
        }
    };

    
});

【这次的逻辑基本是在ubCtrl里面实现的了,后面写的$scope我就不写出来多余了。】

可以看到showList的实现:

currentType变量设置为对应的type,list变量则获取对应type的url列表,后面的判断是为了当选择的类型没有数据的时候,则刷新数据(这是在写的过程中遇到的逻辑问题处理。)

可以看到js的处理也很简单。

再来看列表的模板:

<div class="list">
    <p class="item" ng-repeat="item in list">
        <a href=""></a>
        <a class="close" ng-click="deleteItem(item.id)">删除</a>
    </p>
</div>

没有特殊的逻辑,跟类型列表一样,遍历list变量,展示信息,这里我们看点击删除的操作:

$scope.deleteItem = function(id){
        //console.log(id);
        gData.dataAction('delete',id);
        //console.log($scope.list);
        if($scope.list.length==1){
            refreshTypes();
        }
        $scope.list = gData.getByType($scope.currentType);
    };

这里有一些特殊处理了,因为angular是双向绑定,但我这里对list做了多次赋值操作,这就导致了修改某次列表对应的变量,并不会带来dom的一个相应,所以需要对list做一次重新赋值; 并且,在这里删除时赋值前作的list.length判断,其实是针对于,当我点击某个类型的数据展示的时候,我点击删除其中一个数据,此时list并没有对应删除数据,而我要判断当某个类型数据都删完时,需要刷新类型数据,此时,只能先做判断【我感觉是我对angular应用规范不熟悉,所以遇到bug只能暂时这么处理着先】

那么到此为止,剩下的就是表单的实现了。先看模板:

<div class="add-form">
    <div class="form-line">
        <label for="url_input">URL&ensp;:</label>
        <input type="text" id="url_input" ng-model="url.link" placeholder="请输入url">
    </div>
    <div class="form-line">
        <label for="desc_input">描述:</label>
        <input type="text" id="desc_input" ng-model="url.desc" placeholder="请输入描述">
    </div>
    <div class="form-line">
        <div class="styled-select blue semi-square">
            <select id="type_input" ng-model="listType" ng-init="listType=types2Select[0]" ng-options="type.label for type in types2Select" ng-change="selectType()">
            </select>
        </div>
    </div>
    <div class="form-line" ng-class="{hidden:!showNewType}">
        <label for="new_type_input">类型:</label>
        <input type="text" id="new_type_input" ng-model="url.type" placeholder="请输入新类型">
    </div>
    <button class="btn" ng-click="addUrl(url)">提交</button>
</div>

这个内容比较多了,多出的知识点: ng-model锁定了url对象的某个属性,这样我们在点击提交按钮的触发函数里直接可以用url这个作为实参来传递整个数据组合

再看select标签,这里用了ngInitngOptions指令还有ngChange 最后那个是change事件触发跟click差不多,

ngOptions则跟ngRepeat有点相似,不过这个用于select标签,语法细化到输出label和value两个值。 看模板上的使用:

<select id="type_input" ng-model="listType" ng-init="listType=types2Select[0]" ng-options="type.label for type in types2Select" ng-change="selectType()">
</select>

将生成option标签值为type,text文本为type.labelngOption除了可以遍历数组, 还能遍历对象的,具体用法可看angular官网。

ngInit则是能让你在模板上使用表达式,比方这里模板上设置了对应的listType,但js里面并没有往$scope.listType设置初始值的时候,可在ngInit处设置listType的值。

    //选择类型响应
    $scope.selectType = function(){
        //console.log($scope.listType); 
        $scope.url.type = '';
        if(!$scope.listType){
            $scope.listType = $scope.types2Select[0];
        }
        if($scope.listType.key=='add-new'){
            $scope.showNewType = true;
            return;
        }
        $scope.showNewType = false;
    };

    //提交新url
    $scope.addUrl = function(url){
        if($scope.listType.key!='add-new'){
            url.type = $scope.listType.key;
            gData.dataAction('add',url);
        }else{
            gData.dataAction('add',url);
            refreshTypes();
        }
        
        $scope.url = {
            link:'',
            desc:'',
            type:''
        };
    }

这里js只要实现触发change处理,以及按钮提交处理,遇到的逻辑坑是改变绑定数据导致ngChange,此时ngInit早已过了执行(不会再执行),需要代码做判断初始化model数据。

也是第一次用angular来做一点点小实践,可能理念不一样,一个是对双向绑定,不好适应,所以也导致实现过程中,各种逻辑要写一些判断处理来修复bug,但相应的js代码量可观,只是加上模板的话,就不一定有所优势。

模板:

    <div id="content">
        <div class="url-box" ng-controller="ubCtrl">
            <div class="add-form">
                <div class="form-line">
                    <label for="url_input">URL&ensp;:</label>
                    <input type="text" id="url_input" ng-model="url.link" placeholder="请输入url">
                </div>
                <div class="form-line">
                    <label for="desc_input">描述:</label>
                    <input type="text" id="desc_input" ng-model="url.desc" placeholder="请输入描述">
                </div>
                <div class="form-line">
                    <div class="styled-select blue semi-square">
                        <select id="type_input" ng-model="listType" ng-init="listType=types2Select[0]" ng-options="type.label for type in types2Select" ng-change="selectType()">
                        </select>
                    </div>
                </div>
                <div class="form-line" ng-class="{hidden:!showNewType}">
                    <label for="new_type_input">类型:</label>
                    <input type="text" id="new_type_input" ng-model="url.type" placeholder="请输入新类型">
                </div>
                <button class="btn" ng-click="addUrl(url)">提交</button>
            </div>
            <div class="all-list">
                <div class="list-type">
                    <a class="type-item" ng-repeat="type in types" ng-class="{active:currentType==type}"  ng-click="showList(type)"></a>
                </div>
                <div class="list">
                    <p class="item" ng-repeat="item in list">
                        <a href=""></a>
                        <a class="close" ng-click="deleteItem(item.id)">删除</a>
                    </p>
                </div>
            </div>
        </div>
    </div>

js:

/*data*/
var gData = {
    data:[{
        id:1,
        type:'search',
        link:'http://baidu.com',
        desc:'百度'
    },{
        id:2,
        type:'search',
        link:'http://google.com',
        desc:'google'
    },{
        id:3,
        type:'sns',
        link:'http://facebook.com',
        desc:'facebook'
    },{
        id:4,
        type:'sns',
        link:'http://weibo.com',
        desc:'微博'
    },{
        id:5,
        type:'infos',
        link:'http://qq.com',
        desc:'QQ'
    },{
        id:6,
        type:'infos',
        link:'http://sina.com',
        desc:'渣浪'
    },{
        id:7,
        type:'infos',
        link:'http://yahoo.com',
        desc:'雅虎'
    },{
        id:8,
        type:'knowledge',
        link:'http://sf.gg',
        desc:'sf'
    },{
        id:9,
        type:'knowledge',
        link:'http://zhihu.com',
        desc:'知乎'
    }],
    getTypeList:function(){
        var types=[];
        this.data.forEach((e,i)=>{
            if(types.indexOf(e.type)==-1){
                types.push(e.type);
            }
        });
        return types;
    },
    getByType:function(type){
        //console.log('get list call');
        if(type==undefined||type==""){
            return this.data;
        }
        return this.data.filter(function(e){
            return type==e.type;
        });
    },
    add:function(obj){
        var descData = this.data.slice(0).sort(function(a,b){ return b.id-a.id;});
        //console.log(descData);
        if(descData.length==0){
            descData[0] = {
                id:0
            };
        }
        obj.id = descData[0].id+1;
        console.log(obj.id);
        this.data.push(obj);

    },
    delete:function(id){
        var index = -1;
        this.data.forEach(function(e,i){
            if(e.id==id){
                index = i;
            }
        });
        if(index==-1){
            return;
        }
        this.data.splice(index,1);
    },
    dataAction:function(fnType){
        var arg = [].slice.call(arguments, 1);
        try{

            switch(fnType){
                case 'add':
                    this.add(arg[0]);
                    break;
                case 'delete':
                    this.delete(arg[0]);
                    break;
                default:
                    return;
            }
        }catch(e){
            return;
        }
        this.refresh();
    },
    refresh:function(){
        return;
        if(window.localStorage){
            localStorage.setItem('urls', JSON.stringify(this.data));
        }
    }
};

if(window.localStorage){
    var store = localStorage.getItem('urls');
    try{
        store = JSON.parse(store);
        if(store && store.length){
            gData.data = store;
        }
    }catch(err){
        console.log(err);
    }
}
/*end data*/

var app = angular.module('ubApp', []);

app.controller('ubCtrl', function($scope){
    var refreshTypes = function(){
        $scope.types = gData.getTypeList();
        $scope.types2Select = $scope.types.map(function(e,i){
            var typeObj = {};
            typeObj['label'] = e;
            typeObj['key'] = e;
            return typeObj;
        }).concat({
            label:'新增类型',
            key:'add-new'
        });
        $scope.currentType = '';
        //console.log($scope.types.length);
    };

    $scope.list = gData.getByType();
    
    refreshTypes();


    $scope.url = {
        link:'',
        desc:'',
        type:''
    };

    
    //console.log($scope.types2Select);

    //$scope.listType = $scope.types[0];
    //删除item
    $scope.deleteItem = function(id){
        //console.log(id);
        gData.dataAction('delete',id);
        //console.log($scope.list);
        if($scope.list.length==1){
            refreshTypes();
        }
        $scope.list = gData.getByType($scope.currentType);
    };
    //显示新增类型输入框
    $scope.showNewType = $scope.types2Select[0]['key']=='add-new';

    //选择类型响应
    $scope.selectType = function(){
        //console.log($scope.listType); 
        $scope.url.type = '';
        //在删掉某个分类的时候,刷新分类数据,导致dom触发ngChange
        //由于此时ngInit不会执行 ,所以这里要做listType初始化
        if(!$scope.listType){
            $scope.listType = $scope.types2Select[0];
        }
        if($scope.listType.key=='add-new'){
            $scope.showNewType = true;
            return;
        }
        $scope.showNewType = false;
    };

    $scope.showList = function(type){
        $scope.currentType = type;
        $scope.list = gData.getByType(type);
        if($scope.list.length==0){
            refreshTypes();
        }
    };

    //提交新url
    $scope.addUrl = function(url){
        if($scope.listType.key!='add-new'){
            url.type = $scope.listType.key;
            gData.dataAction('add',url);
        }else{
            gData.dataAction('add',url);
            refreshTypes();
        }
        //提交后清空输入框内容
        $scope.url = {
            link:'',
            desc:'',
            type:''
        };
    }
});