angular高级篇之transclude使用详解

angular指令的transclude属性是一个让初学者比较难以理解的地方,transclude可以设置为false(默认),true或者对象三种值,如果不设该属性就默认为false,也就是说你不需要将该指令所在元素包含的内容嵌入到模板中。

当transclude为true的时候,这时指令所在元素包含的内容会被嵌入到模板中有ng-transclude指令的元素中,例如:

index.html

<!DOCTYPE html>
<html ng-app="myapp">
<head>
    <meta charset="utf-8">
    <title>angular test</title>
</head>
<body ng-controller="myCtrl">
    <div hello="{{name}}">你好</div>
</body>
<script src="./node_modules/angular/angular.js"></script>
<script src="./index.js"></script>
</html>

index.js

let app = angular.module('myapp',[]);
app.controller('myCtrl', $scope =>{
    $scope.name = "Jhon";
});
app.directive('hello', () =>{
    return {
        restrict: 'A',
        template: '<div><span ng-transclude></span>{{name}}</div>',
        transclude: true,
        scope:{
            name: "@hello"
        }
    }
});

运行之后的效果如下:

<div hello="Jhon" class="ng-isolate-scope">
    <div class="ng-binding">
        <span ng-transclude="">你好</span>
        Jhon
    </div>
</div>

当指令元素包含的内容需要嵌入到指令模板不同地方的时候,这个时候就要把transclude设置为对象,例如下面这个我在项目中使用的一个例子:

index.html

<!DOCTYPE html>
<html ng-app="myapp">
<head>
    <meta charset="utf-8">
    <title>angular test</title>
</head>
<body ng-controller="myCtrl">
    <panel>
        <panel-header>{{title}}</panel-header>
        <panel-body>{{content}}</panel-body>
        <panel-footer>{{footer}}</panel-footer>
    </panel>
</body>
<script src="./node_modules/angular/angular.js"></script>
<script src="./index.js"></script>
</html>

index.js

let app = angular.module('myapp',[]);
app.controller('myCtrl', ['$scope', $scope =>{
    $scope.title = "标题";
    $scope.content = "内容";
    $scope.footer = "页脚";
}]);
app.directive('panel', () =>{
    return {
        restrict: 'E',
        replace: true,
        transclude: {
            'header': '?panelHeader',
            'body': 'panelBody',
            'footer': '?panelFooter'
        },
        template: `
            <div class="panel">
                <div class="panel-header" ng-transclude="header"></div>
                <div class="panel-body" ng-transclude="body"></div>
                <div class="panel-footer" ng-transclude="footer"></div>
            </div>`
    }
});

显示结果如下:

<div class="panel">
    <div class="panel-header" ng-transclude="header">
        <panel-header class="ng-binding ng-scope">
            标题
        </panel-header>
    </div>
    <div class="panel-body" ng-transclude="body">
        <panel-body class="ng-binding ng-scope">
            内容
        </panel-body>
    </div>
    <div class="panel-footer" ng-transclude="footer">
        <panel-footer class="ng-binding ng-scope">
            页脚
        </panel-footer>
    </div>
</div>

这里指令元素内部有三个指令,这三个指令必须以E的形式调用,它们分别要插入到模板的不同位置,tranclude指定了要插入的位置,transclude是一个键值对的对象,key指定了要插入模板的位置,value就是要插入的内容,?代表这个嵌入点不一定有指令存在,否则必须在这个点插入指令,不然会报错。

值得注意的是,这个实例也证明了一点,指令包含的元素的作用域继承自指令的父作用域而不是隔离作用域。

 

除了使用ng-transclude指令指定内容嵌入的地方外,我们还有两种方法可以做到这点。

第一种就是在控制器中使用$transclude服务,例如以下代码:

index.html

<!DOCTYPE html>
<html ng-app="myapp">
<head>
    <meta charset="utf-8">
    <title>angular test</title>
</head>
<body ng-controller="myCtrl">
    <hello name="{{name}}"><span>{{action}}</span></hello>
</body>
<script src="./node_modules/angular/angular.js"></script>
<script src="./index.js"></script>
</html>

index.js

let app = angular.module('myapp',[]);
app.controller('myCtrl', ['$scope', $scope =>{
    $scope.name = "Jhon";
    $scope.action = "你好";
}]);
app.directive('hello', () =>{
    return {
        restrict: 'E',
        transclude: true,
        controller: ['$scope', '$element', '$transclude', ($scope, $element, $transclude) =>{
            $transclude(clone =>{
                //$element.find只能通过标签名进行查找
                $element.find('span').append(clone);
            });
        }],
        template: '<div><span></span>{{name}}</div>',
        scope: {
            name: '@'
        }
    }
});

最后显示的结果如下:

<hello name="Jhon" class="ng-isolate-scope">
    <div class="ng-binding">
        <span>
            <span class="ng-binding ng-scope">
                你好
            </span>
        </span>
        Jhon
    </div>
</hello>

其中控制器中的$element就是编译了之后的模板,而$transclude中的参数clone则是被编译了的指令包含的内容。两者可以同时对模板和内容进行处理。

 

另一种方法是在compile中指定第三个transcludeFn参数,如下所示:

 index.js

let app = angular.module('myapp',[]);
app.controller('myCtrl', ['$scope', $scope =>{
    $scope.name = "Jhon";
    $scope.action = "你好";
}]);
app.directive('hello', () =>{
    return {
        restrict: 'E',
        transclude: true,
        compile: (tElement, tAttrs, transcludeFn) =>{
            return (scope, element, attrs) =>{
                scope.action = "hello";
                transcludeFn(scope, (clone) =>{
                    element.find('span').append(clone);
                });
            };
        },
        template: '<div><span></span>{{name}}</div>',
        scope: {
            name: '@'
        }
    }
});

这个文件实现了以上控制器中相同的功能,transcludeFn接受两个参数,一个scope作用域,一个函数,和controller一样,这个函数的参数clone就是编译之后的要嵌入的内容。唯一不同的是,编译这个clone的作用域是传进去的第一个参数,而controller中clone是继承了指令的父作用域。

 

posted @ 2017-09-20 09:45  zc的救赎  阅读(2620)  评论(0编辑  收藏  举报