执行/渲染来自另一个指令的指令

execute/render a directive from another directive

本文关键字:指令 另一个 执行      更新时间:2023-09-26

我有以下两个指令。在DirectiveA中,我从远程服务器获得一些数据,然后将该数据渲染为anchor标签的模板。现在,当用户点击任何链接,我broadcast事件,并听取该事件在DirectiveB。在DirectiveB我想使另一个ajax请求,当我收到响应的数据,然后我要呈现DirectiveB模板。我目前的方法不起作用,因为它在开始时执行两个指令,当时我没有DirectiveB的任何数据。下面是代码

DirectiveA

angular.module('app').directive('DirectiveA', function ($http) {
    'use strict';
    return {
        restrict: 'E',
        templateUrl: '/templates/templateA.html',
        controller: function ($scope) {
            $scope.showDetails = function (num) { // <-- this executes in ng-click in template
                $scope.$broadcast('season', num);
            };
        },
        link: function (scope, element, attributes) {
            $http.get(attributes.resource).then(function (response) {
                scope.rows = response.data;
                scope.seasons = [];
                for (var i = 0; i < scope.rows.length; i++) {
                    var num = parseInt(scope.rows[i].data);
                    if (year >= 2005 && year <= 2015) {
                        scope.seasons.push({ 'data': scope.rows[i].data });
                    }
                }
            });
        }
    };
});

这是DirectiveB

angular.module('app').directive('DirectiveB', function() {
    return {
        restrict: 'E',
        templateUrl: 'templates/templateB.html',
        controller: function($scope, $http) {
            $scope.$on('season', function ($scope, num) { // I listen to that event
                $http.get('http://demo.com/api/' + num).then(function (response) {
                    $scope.standings = response.data;
                });
            });
        }
    };
});

以下是我在HTML 中使用它的方法
<directive-a resource="http://demo.com/api/>
</directive-a>
<directive-b></directive-b>

我还在等待它的解决方案

更新3

templateA

<ul>
    <li ng-repeat="row in seasons"> 
        <a href="#" ng-click="showDetails(row.season)">{{ row.season }}</a>
    </li>
</ul>
模板B

<h3>Standings</h3>
<table>
    <thead>
        <th ng-repeat="standing in standings" ng-if="state">{{ position  }}</th>
    </thead>
    <tbody>
        <tr ng-repeat="row in standings">
            {{ row.Driver  }}
        </tr>
    </tbody>
</table>

你的问题归结为:

  1. 如何保证DirectiveADirectiveB之间的通信

    由于您的两个指令使用相同的作用域,您的解决方案与事件广播工作。一旦您将DDO中的scope设置更改为false以外的任何其他设置,这种情况就会中断。

    这就是为什么我建议你在$rootScope$broadcast事件

  2. 一旦数据准备好,如何渲染DirectiveB的模板

    一个简单的ngIf封装指令的模板就可以完成这项工作。我们不需要任何花哨的东西

Bellow是一个简单的工作示例。注意这两个指令是如何使用隔离作用域的——这只是为了展示为什么需要$rootScope.$broadcast。我还使用$timeout来模拟服务器的延迟。

angular
  .module('myApp', [])
  .directive('directiveA', function() {
    return {
      templateUrl: 'tempA.html',
      controller: function($rootScope, $scope) {
        $scope.sendMessage = function(message) {
          $rootScope.$broadcast('eventName', message);
        };
      },
      scope: {}
    };
  })
  .directive('directiveB', function() {
    return {
      templateUrl: 'tempB.html',
      controller: function($rootScope, $scope, $timeout) {
        $scope.messages = [];
        $rootScope.$on('eventName', function(event, message) {
          $timeout(function() {
            $scope.messages.push(message);
          }, 50);
        });
      },
      scope: {}
    };
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<div ng-app="myApp">
  <directive-a></directive-a>
  <directive-b></directive-b>
  <script type="text/ng-template" id="tempA.html">
    <h2>Hello from DirectiveA</h2>
    <label>
      <p>Message to send to DirectiveB</p>
      <input type="text" ng-model="toSend">
    </label>
    <button type="button" ng-click="sendMessage(toSend); toSend = '';">Send</button>
  </script>
  <script type="text/ng-template" id="tempB.html">
    <div ng-if="messages.length > 0">
      <h3>Hello from DirectiveB</h3>
      <ul>
        <li ng-repeat="message in messages track by $index">{{ message }}</li>
      </ul>
    </div>
  </script>
</div>

使用API (angular服务)在你的指令之间进行通信。在我的例子中,我称之为MyAPIService:

angular.module('app').service('MyAPIService', function () {
   this.display = false;
   this.seasons = [];
});

然后在指令和指令A中注入你的API实例,当你点击锚点时,你必须填充你的季节并更新你的"display"变量。在你的指令下,只需观察"display"变量

angular.module('app').directive('DirectiveA', function ($http, MyAPIService) {
'use strict';
return {
    restrict: 'E',
    templateUrl: '/templates/templateA.html',
    controller: function ($scope) {
        $scope.showDetails = function () { 
           MyAPIService.display = true;
    },
    link: function (scope, element, attributes) {
        $http.get(attributes.resource).then(function (response) {
            scope.rows = response.data;
            MyAPIService.seasons = [];
            for (var i = 0; i < scope.rows.length; i++) {
                var num = parseInt(scope.rows[i].data);
                if (year >= 2005 && year <= 2015) {
                    MyAPIService.seasons.push({ 'data': scope.rows[i].data });
                }
            }
        });
    }
};

});

angular.module('app').directive('DirectiveB', function() {
   return {
       restrict: 'E',
       templateUrl: 'templates/templateB.html',
       controller: function($scope, $http, MyAPIService) {
           // WATCH "DISPLAY" VARIABLE AND CALL YOUR AJAX
       }
   };
});

我有一个工作的解决方案给你,有点不同于你的广播方法。正如你所提到的,你希望在点击指令a之后才加载指令B的数据。所以,我们将添加一个条件来从DOM中删除指令B。当点击指令A触发时,我们将使指令B加载到DOM中。这样你的指令B的控制器将在稍后的时间点被加载。

下面是支持我的参数 的代码指令

angular.module('app').directive('DirectiveA', function ($http) {
'use strict';
return {
    restrict: 'E',
    templateUrl: '/templates/templateA.html',
    controller: function ($scope) {
        $scope.showDetails = function (num) { // <-- this executes in ng-click in template
            $scope.isDirectiveBVisible = true;
        };
    },
    link: function (scope, element, attributes) {
        $http.get(attributes.resource).then(function (response) {
            scope.rows = response.data;
            scope.seasons = [];
            for (var i = 0; i < scope.rows.length; i++) {
                var num = parseInt(scope.rows[i].data);
                if (year >= 2005 && year <= 2015) {
                    scope.seasons.push({ 'data': scope.rows[i].data });
                }
            }
        });
    }
};
});
B指令

angular.module('app').directive('DirectiveB', function() {
return {
    restrict: 'E',
    templateUrl: 'templates/templateB.html',
    controller: function($scope, $http) {
            $http.get('http://demo.com/api/' + num).then(function (response) {
                $scope.data = response.data;
            });
    }
};
});
HTML

<directive-a resource="http://demo.com/api/>
</directive-a>
<directive-b ng-if="isDirectiveBVisible"></directive-b>

很久以前,我创建了这个解决方案,就像实验

<div ng-directive="{{directive}}"></div>

可以动态渲染指令。

但是你的问题不是关于动态渲染的。是关于改变组件(指令)的状态

你已经提供了关于使用ngIf的答案,这个东西只在放它的地方。我建议把它放在你的指令中,并为整个模板制作包装器:

<div ng-if="isShown">
  <h3>Standings</h3>
  ...
</div>

并在需要的地方设置isShown scope属性,例如在事件之后:

 $scope.$on('season', function ($scope, num) { 
    $scope.isShown = true;   
 });

或ajax调用后:

$http.get('http://demo.com/api/' + num).then(function (response) {
    $scope.isShown = true;
});
此外,我建议不仅要使用可见性,还要使用加载指示来显示它正在加载:
<div ng-if="isShown">
   <loading-indicator ng-if="isLoading">
   <div ng-if="!isLoading">
     <h3>Standings</h3>
     ...
   </div>
</div>

您可以尝试在指令A中使用ng-if和某种标志来指示ajax请求已经完成。Ng-if将隐藏的元素包装在注释中,这样在条件为真之前,它们就不会出现在dom中。

From "AngularJs $scope documentation"

向所有子作用域(及其子作用域)分配一个事件名,通知已注册的$rootScope。听众范围。

这意味着你的directiveB必须是你的directiveA的子级才能捕捉到广播事件。例如:

<directive-A>
    <directive-B></directive-B>
</directive-A>

指令的控制器的函数代码将在html渲染之前执行,而链接的函数代码将在html渲染之后执行。这意味着您不能处理directiveB的执行。当您有指令a中调用的响应时,您可以简单地创建它。例如:

scope.data={resolved:false};
$http.get(attributes.resource).then(function (response) {
                scope.rows = response.data;
                scope.seasons = [];
                scope.data.resolved = true;
                for (var i = 0; i < scope.rows.length; i++) {
                    var num = parseInt(scope.rows[i].data);
                    if (year >= 2005 && year <= 2015) {
                        scope.seasons.push({ 'data': scope.rows[i].data });
                    }
                }
            });

在上面的HTML示例中:

<directive-A>
    <directive-B ng-if="data.resolved"></directive-B>
</directive-A>

通过这种方式,当您的调用被解决时,您的directiveB将被创建。

注意:如果你使用ngIf,你可能会丢失$broadcast事件,因为你的directiveB的$on会在$broadcast

之后创建

我认为最好的解决方案,如果你想保留两个指令,是在你的指令B模板中使用ng-if指令,只在有结果时显示表。

你只需要修改你的指令B模板来添加一个条件到你的当前表,并添加一个<div>或类似的,当用户还没有选择任何项目时显示消息。

你的指令B模板应该是:

<h3>Standings</h3>
<!-- Add ng-if here -->
<table ng-if="standings.length > 0">
    <thead>
        <th ng-repeat="standing in standings" ng-if="state">{{ position  }}</th>
    </thead>
    <tbody>
        <tr ng-repeat="row in standings">
            {{ row.Driver  }}
        </tr>
    </tbody>
</table>
<!-- and also here -->
<!-- If standings is not an array -->
<div ng-if="!standings.length">
    <p>Please, select one of the items above to show standings.</p>
</div>

您可以根据需要添加任意多的带有条件的div。例如,当所选项目没有结果时,您可以添加一条消息:

<!-- If standings is an array but it is empty -->
<div ng-if="standings.length && standings.length > 0">
    <p>There are no standings to show.</p>
</div>