Create a container directive in angularjs

♀尐吖头ヾ 提交于 2019-12-04 17:24:20

I do something that I think is similar. Let me know if I've somehow missed the point. I have a directive that does a transcluded ng-repeat based on remote data. Here's how it works.

Update

It's the template in the page markup that's the issue. However, if you want the ng-repeat template to exist on the same page markup, you can do this:

<script type="text/ng-template" id="navbar.html">
    <li ng-repeat="item in items" ng-class="{active: item.selected}">
        <a href="/{{item.link}}">{{item.title}}</a>
    </li>
</script>

Not exactly the same thing, but it get's you the same effect - template on the same page as the directive - just not nested with it.

Update End

I have the same array in the parent and the child scopes: i.e. $scope.items. Because it's a reference type, through prototypical inheritance, both scopes reference the same object. In the location that doesn't update the property, I initialize it like this $scope.items = $scope.items || []; -- i.e. if the property hasn't been initialized, initialize it, otherwise keep it.

directive('navbar', ['$location', '$http',  function ($location, $http) {
    return {
        restrict: 'E',
        transclude: true,
        scope: { heading: '@'},
        controller: 'NavbarCtrl',
        templateUrl: 'navbar.html',
        replace: true,
        link: function ($scope, $element, $attrs, navbarCtrl) {

            $scope.items = [];
            $scope.heading = $scope.heading || $attrs.heading;

            $http.get(itemsUrl).success(function(data) {
                $scope.items = ... async get of data ... ;
                navbarCtrl.selectByUrl($location.absUrl());
            });

            $scope.$watch('$location.absUrl()', function (locationPath) {
                navbarCtrl.selectByUrl(locationPath)
            });
        }
    }
}])

The directive's $watch calls a controller function, and that function has access to the controller $scope through its closure.

function NavbarCtrl($scope, $timeout, $http, $location, $attrs) {
    $scope.items = $scope.items || [];

    this.select = $scope.select = function (item) {
        angular.forEach($scope.items, function (item) {
            item.selected = false;
        });
        item.selected = true;
    };

    this.selectByUrl = function (url) {
        angular.forEach($scope.items, function (item) {
            if ('http://' + item.link === url) {
                $scope.select(item);
            }
        });
    };
}

Then, in my template, which I transclude, I have:

<li ng-repeat="item in items" ng-class="{active: item.selected}">
    <a href="/{{item.link}}">{{item.title}}</a>
</li>

In the page markup, I use it like this:

<div ng-controller="NavbarCtrl">
    <navbar heading="Navbar Heading"/>
</div>

Custom repeater...

Building a custom repeater is a complicated task. Mostly because of performance issues but also because it should play well with other directives.

If you really need to build one, You must first dive into ngRepeat source code to understand some considerations and then to mutate it to your own needs.

ngRepeat now uses $watchCollection (since 1.2) which replaced the deep $watch hog.


Solution

So my recommendation is do not build a custom repeater , use ngRepeat!

I still don't know what you want to achieve but this is the construction.

Here is a plunker: http://plnkr.co/edit/pziqRzz0i1mU6eG5lAmd?p=preview

create another custom directive instead of ngTransclude

app.directive('myTransclude',function(){
  return {
    require: "^myColumnLayout",
    link: function(scope,elm,attr,ctrl,$transclude){
      $transclude(function(clone){
        elm.empty();
        elm.append(clone);
        ctrl.do("Hi")
      })
    }
  }
});

create your directive with ng-repeating my-transclude and a controller:

app.directive('myColumnLayout', function() {
  return {
    restrict: 'EA',
    transclude: true,
    controller: function(){
      this.do = function(x) {
        console.log(x)
      }
    },
    template: '<ul><li ng-repeat="item in collection track by $index" my-transclude></li></ul>',
    scope: {
      collection: '='
    }
  }
});

why?

  • Now you have total control of the transclusion phase inside my-transclude
  • you can define on the controller anything you need to share into the transcluded content.
  • You are not getting your hands dirty with repeating stuff.

It's not clear for me what exactly you do but i fix some mistake in your directive as follows.

  1. Clear the elem children before recreating them.
  2. Note the third parameter of $watch function. It is necessary for watching a collection.

    app.directive('myColumnLayout', function () {
        return {
            restrict: 'EA',
            transclude: true,
            scope: {
                collection: '='
            },
            link: function (scope, elem, attrs, container, transclude) {
                scope.$watch('collection', function (newVal, oldVal) {
                    elem.empty();
                    for (var i = 0; i < newVal.length; i++) {
                        var li = angular.element('<li></li>');
                        var scp = scope.$parent.$new();
                        scp.item = newVal[i];
                        transclude(scp, function (clone) {
                            elem.append(clone);
                        });
                    }
                }, true);
            }
        }
    });
    
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!