【问题标题】:Angular custom Collapsable directive fails when wrapped around ng-repeat content包裹 ng-repeat 内容时,Angular 自定义 Collapsable 指令失败
【发布时间】:2015-08-25 14:23:39
【问题描述】:

我编写了一个自定义 collapsable 指令,它将现有视图包装到一个容器中,该容器允许另一个指令,称为 collapser-button,它实际上试图隐藏容器由 collapsable 指令提供。

可折叠

myApp.directive('collapsable', [
    '$compile',
    function ($compile) {
        return {
            scope: {
                screenId: '@'
            },
            restrict: 'E',
            controller: [
                '$scope',
                function ($scope) {
                    var self = {};

                    self.GetContainer = function (innerHtml) {
                        console.log(innerHtml);
                        var html = "<div id='" + $scope.id + "'>" + innerHtml + "</div>";

                        return html;
                    };

                    self.DoCallBack = function (targetContainerId) {
                        var container = $('#' + targetContainerId);

                        if (container.length > 0) {
                            container.hide("slow");
                        }
                    }

                    // --- //

                    $scope.GetContainer = self.GetContainer;

                    $scope.reference = {
                        doCallBack: self.DoCallBack, // This is a function parameter, DONT use () !!!
                        screenId: $scope.screenId
                    }
                }
            ],
            link: function ($scope, $elem, $attrs) {
                var html = $scope.GetContainer($elem.html());

                var linkFn = $compile(html);
                var content = linkFn($scope);

                $elem.html(content);
            }
        }
    }
]);

折叠按钮

myApp.directive('collapserButton', [
    '$compile',
    function ($compile) {
        return {
            restrict: 'E',
            scope: {
                parent: '='
            },
            transclude: true,
            controller: [
                '$scope',
                function ($scope) {
                    var self = {};

                    self.HandleButtonClick = function () {
                        $scope.parent.doCallBack($scope.parent.screenId);
                    }

                    // --- SCOPE --- //

                    $scope.HandleButtonClick = self.HandleButtonClick;
                }],
            link: function($scope, $elem, $attrs) {
                var html = '<button class="btn btn-primary" ng-click="HandleButtonClick()">Collapse</button>';
                var linkFn = $compile(html);
                var content = linkFn($scope);
                $elem.html(content);
            }
        }
}]);

用法

<collapsable screenid="screen1">
  ...
  ...
  ...
  <collapser-button parent="reference"/>
</collapsable>

这适用于静态测试设置,现在我想将其应用到实际用例中:

我有一个包含以下内容的(单页)视图:

<div ng-controller="personController">
    <table>
        <thead>
            <tr>
                <th style="width: 33%" translate>Firstname</th>
                <th style="width: 33%" translate>Firstname2</th>
                <th style="width: 34%" translate>Name</th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="person in people">
                <td>{{ person.Firstname }}</td>
                <td>{{ person.Firstname2 }}</td>
                <td>{{ person.Name }}</td>
            </tr>
        </tbody>
    </table>
</div>

它本身可以完美运行,但是,如果我将 collapsable 指令包裹在它周围,则翻译和 ng-repeater 将不再起作用。

您可能已经注意到,'collapsable' 的 GetContainer 函数将 innerHtml 记录到控制台,对于当前设置,返回以下信息:

<div ng-controller="personController" class="ng-scope">
    <table>
        <thead>
            <tr>
                <th style="width: 33%" translate="" class="ng-scope">Firstname</th>
                <th style="width: 33%" translate="" class="ng-scope">Firstname2</th>
                <th style="width: 34%" translate="" class="ng-scope">Name</th>
            </tr>
        </thead>
        <tbody>
            <!-- ngRepeat: persoon in personen -->
        </tbody>
    </table>
</div>

关于如何解释/解决此问题的任何想法?

更新

这变得很奇怪,如果我在可折叠指令的链接中添加一个不存在的函数,那么 ng-repeat 会正确呈现:

link: function($scope, $elem, $attrs) {
    var html = '<button class="btn btn-primary" ng-click="HandleButtonClick()">Collapse</button>';
    var linkFn = $compile(html);
    var content = linkFn($scope);

    FunctionThatDoesNotExist();

    $elem.html(content);
}

正如我所说,它现在已正确呈现,但这并不意味着它已修复,因为折叠按钮的父引用未定义,尽管它已在父可折叠指令中正确定义。

我“认为”的是 $compile 操作不能完全 100% 工作,即。并非所有 Angular 逻辑都被激活。

Plunkr

Plunkr

如果我将第一个 Blah()(在 script.js 中,第 76 行)放在注释中,我会得到 [[object HTMLDivElement]]:

link: function ($scope, $elem, $attrs) {
    var html = $scope.GetContainer($elem.html());

    var linkFn = $compile(html);
    var content = linkFn($scope);

    //Blah();

    $elem.html(content);
}

【问题讨论】:

  • 你能提供样品plunkr吗?
  • 我看看我能做什么。
  • 所以,你做错了:-) html 方法可以访问 string 或函数,但你尝试将 html 元素传递给它。如果将html 更改为append,则可以修复它
  • 使用 prepend 和 append 是部分修复,还有一些其他错误,但这可能是某种疏忽,我会调查一下。
  • 你的意思是什么错误?

标签: javascript jquery html angularjs asp.net-mvc


【解决方案1】:

您有一些错误,而且在大多数情况下,您不需要手动操作 DOM。
所以,首先,你使用了错误的html 方法。 angular.element 是一个 jqLite 元素,它实现了一些来自 jQuery 的方法,所以 element.htmljQuery.html 相同,所以,如您所见,您只能传递给它 string功能

.html( htmlString )
.html( function )

但你尝试通过 - html 元素,它会隐式转换为字符串并显示 [[object HTMLDivElement]]

您也不需要获取内容和手动添加它,因为内置transclude可以自动完成,您只需添加模板即可。

另外,您不需要在collapserButton 中引用父级,因为您可以直接从父级控制器获取所需的值。

所以,你的程序可以简化成这样

  angular.module('ngApp', [])
    .directive('collapsable', [
      '$compile',
      function($compile) {
        return {
          scope: {
            screenId: '@'
          },
          restrict: 'E',
          transclude: true,
          template: '<div id="{{screenId}}"><ng-transclude></ng-transclude></div>',
          controller: function($scope) {
            this.DoCallBack = function(targetContainerId) {
              var container = $('#' + targetContainerId);

              if (container.length > 0) {
                container.hide("slow");
              }
            }

            // --- //

            this.screenId = $scope.screenId;
          }
        }
      }
    ])
    .directive('collapserButton', [
      '$compile',
      function($compile) {
        return {
          restrict: 'E',
          require: '^?collapsable',
          template: '<button class="btn btn-primary" ng-click="HandleButtonClick()">Collapse</button>',
          link: function($scope, $elem, $attrs, $ctrl) {
            $scope.HandleButtonClick = function() {
              $ctrl.DoCallBack($ctrl.screenId);
            }

          }
        }
      }
    ])
    .controller('ngAppController', function($scope) {

      var self = {};

      self.people = [{
        "Firstname": "Jack",
        "Firstname2": "William",
        "Name": "Sparrow",
      }, {
        "Firstname": "Charles",
        "Firstname2": "Foster",
        "Name": "Kane",
      }, {
        "Firstname": "Hannibal",
        "Firstname2": "",
        "Name": "Lecter",
      }];

      // --- //

      $scope.people = self.people;

      $scope.a = 1;
      $scope.b = 2;
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<div ng-app="ngApp">
  <collapsable screen-id="screen1">
    <div ng-controller="ngAppController">
      <table style="width: 300px">
        <thead>
          <tr>
            <th style="width: 33%">Firstname</th>
            <th style="width: 33%">Firstname2</th>
            <th style="width: 34%">Name</th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="person in people">
            <td>{{ person.Firstname }}</td>
            <td>{{ person.Firstname2 }}</td>
            <td>{{ person.Name }}</td>
          </tr>
        </tbody>
      </table>
    </div>
    <collapser-button parent-reference="reference"></collapser-button>
  </collapsable>
</div>

带有ng-hide 且没有动画的示例

  angular.module('ngApp', [])
    .directive('collapsable', [
      '$compile',
      function($compile) {
        return {
          scope: {
            screenId: '@'
          },
          restrict: 'E',
          transclude: true,
          template: '<div id="{{screenId}}" ng-hide="hide"><ng-transclude></ng-transclude></div>',
          controller: function($scope) {
            this.DoCallBack = function(targetContainerId) {
                $scope.hide = true;              
            }

            // --- //

            this.screenId = $scope.screenId;
          }
        }
      }
    ])
    .directive('collapserButton', [
      '$compile',
      function($compile) {
        return {
          restrict: 'E',
          require: '^?collapsable',
          template: '<button class="btn btn-primary" ng-click="HandleButtonClick()">Collapse</button>',
          link: function($scope, $elem, $attrs, $ctrl) {
            $scope.HandleButtonClick = function() {
              $ctrl.DoCallBack($ctrl.screenId);
            }

          }
        }
      }
    ])
    .controller('ngAppController', function($scope) {

      var self = {};

      self.people = [{
        "Firstname": "Jack",
        "Firstname2": "William",
        "Name": "Sparrow",
      }, {
        "Firstname": "Charles",
        "Firstname2": "Foster",
        "Name": "Kane",
      }, {
        "Firstname": "Hannibal",
        "Firstname2": "",
        "Name": "Lecter",
      }];

      // --- //

      $scope.people = self.people;

      $scope.a = 1;
      $scope.b = 2;
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<div ng-app="ngApp">
  <collapsable screen-id="screen1">
    <div ng-controller="ngAppController">
      <table style="width: 300px">
        <thead>
          <tr>
            <th style="width: 33%">Firstname</th>
            <th style="width: 33%">Firstname2</th>
            <th style="width: 34%">Name</th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="person in people">
            <td>{{ person.Firstname }}</td>
            <td>{{ person.Firstname2 }}</td>
            <td>{{ person.Name }}</td>
          </tr>
        </tbody>
      </table>
    </div>
    <collapser-button parent-reference="reference"></collapser-button>
  </collapsable>
</div>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-05
    • 2013-02-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多