AngularJS: Как да предам обект от директивата към включен шаблон

Имам директива, която създава потребителски интерфейс, който позволява на потребителя да извършва търсене. Директивата обвива съдържание, което ще включва и ще се превърне в шаблон за всеки отделен резултат от търсенето. Нещо като това:

<search>
  <div class="someStyle" ng-click="selectResult(result)">{{result.Name}}</div>
</search>

Бих искал ng-click да извика функцията selectResult в обхвата на контролера, но обектът result да идва от директивата. Как мога да постигна това с изолиран обхват в директивата?




Отговори (3)


Вместо да използвате ng-transclude, можете да създадете своя собствена директива за включване на търсене, която може да се използва за поставяне на резултат в обхвата на включването. Например, вашата директива за търсене може да изглежда по следния начин с ng-repeat и директивата search-transclude, където искате включеното съдържание:

.directive("search", function (SearchResults) {
    return {
        restrict: "AE",
        transclude: true,
        scope: {},
        template: '<div ng-repeat="result in results">Search Relevance:' +
        '{{result.relevance}}' +
        //the most important part search-transclude that receives the current
        //result of ng-repeat
        '<div search-transclude result="result"></div></div>',
        link: function (scope, elem, attrs) {
            //get search results
            scope.results = SearchResults.results;
        }
    }
})

Изградете директива за включване на търсене, както следва:

.directive("searchTransclude", function () {
    return {
        restrict: "A",
        link: function (scope, elem, attrs, ctrl, $transclude) {
            //create a new scope that inherits from the parent of the
            //search directive ($parent.$parent) so that result can be used with other
            //items within that scope (e.g. selectResult)
            var newScope = scope.$parent.$parent.$new();
            //put result from isolate to be available to transcluded content
            newScope.result = scope.$eval(attrs.result);
            $transclude(newScope, function (clone) {
                elem.append(clone);
            });
        }
    }
})

Включеното съдържание вече ще може да види функцията selectResult, ако съществува в обхвата, където е създадена директивата за търсене. Пример тук.

person Patrick    schedule 03.12.2014
comment
Опитвам се да вкарам вашия пример в моя код, но $transclude е недефиниран. Някаква идея какво може да правя погрешно? - person adam0101; 04.12.2014
comment
Използвам контролер в директивата за търсене, може ли това да създава проблеми? - person adam0101; 04.12.2014
comment
Също така използвам вложени ng-повтаряния. Може би това го причинява, но не знам как да го поправя. - person adam0101; 04.12.2014
comment
Не, това, което предположихте, че е правилно, просто имам и контролер в директивата и си помислих, че може би това причинява проблем, но го извадих и 5-ият параметър все още идва като „недефиниран“. Използвам версия 1.2.15. Мислите ли, че тъй като използвам вложени ng-повтаряния, ще трябва да използвам и вложени директиви searchTransclude? - person adam0101; 04.12.2014
comment
Може би добавете transclude: true към директивата search-transclude. Имах transclude: true в самата директива за търсене в моя пример. Това трябва да бъде зададено, за да бъде предоставен transcludeFn. - person Patrick; 04.12.2014
comment
Добавянето на transclude: true към search-transclude проработи, за да накара $transclude да не бъде недефиниран, но все още не виждам съдържанието си. Не разбирам защо вашият jsfiddle работи без transclude: true при search-transclude и моят код не. - person adam0101; 04.12.2014
comment
Когато преминавам през кода и гледам elem, той е <div search-transclude result="result"></div>, но clone също е <div search-transclude result="result"></div>. Така че сякаш се прикрепя към себе си. Толкова съм объркана. - person adam0101; 04.12.2014
comment
Можете ли да поставите това, което правите на jsfiddle, за да мога да видя какво става. Примерът, който дадох, работи, но не мога да разработя повече, без да видя вашата настройка. - person Patrick; 04.12.2014
comment
НАЙ-НАКРАЯ го разбрах. transclude:true не е необходим при search-transclude. Проблемът беше причинен от ng-show на външното ng-повторение! Изразът беше верен, самото му съществуване причиняваше грешка! Опитах се да го преместя в родителски div, но пак имах проблеми. Едва след като го промених на ng-if, функцията $transclude и съдържанието се появиха. Нямам представа защо това беше проблем, но поне го накарах да работи. Благодаря. - person adam0101; 05.12.2014
comment
Освен това трябваше да добавя още .$parent в search-transclude поради вложеното ng-repeat. - person adam0101; 05.12.2014

Включеното съдържание винаги ще използва обхвата, в който се намира елементът на директивата, т.е. обхвата на вашия контролер. Ето защо, ако искате аргументът result на функцията selectResult да получи стойността си от изолиран обхват, тогава трябва да установите двупосочно свързване между свойствата result на изолирания обхват и обхвата на контролера. След задаване на свойството result на желаната стойност в изолиран обхват, свойството result обхват на контролера ще бъде актуализирано до същата стойност. Така че включеното съдържание ще използва result на контролера, което е в синхрон с result на изолирания обхват.

1) добавете атрибут resultAttr='result' към елемент на директива.

<search resultAttr='result'> <div class="someStyle" ng-click="selectResult(result)">{{result.Name}}</div> </search>

2) установете двупосочно обвързване за свойството result, когато дефинирате изолиран обхват в директива:

scope: { result: "=resultAttr" }

3) задайте result на някаква стойност в директивата

person Suren Aznauryan    schedule 03.12.2014
comment
Искам да направя ng-repeat в шаблона на директивата и да използвам включеното съдържание като шаблон за всеки резултат. Във вашия пример няма ли всички те да използват един и същ резултатен обект? - person adam0101; 03.12.2014
comment
за случай на ng-repeat няма да работи, защото ng-repeat ще създаде свой собствен обхват като дъщерен обхват на обхвата на директивата и всички result свойства от всяка итерация на ng-repeat ще бъдат в нов дъщерен обхват на директивата. - person Suren Aznauryan; 03.12.2014
comment
имате ли причина да използвате превключване тук? - person Suren Aznauryan; 03.12.2014
comment
Защото искам да използвам повторно директивата за търсене, но да позволя на потребителя на директивата да форматира резултатите, както пожелае. - person adam0101; 03.12.2014
comment
можете да опитате да се отървете от изолирания обхват на директивата и да зададете обхвата на директивата да бъде наследен от контролера (задайте scope: true във вашата директива). След това сменете <div class="someStyle" ng-click="selectResult(result)">{{result.Name}}</div> на <div class="someStyle" ng-click="selectResult($$child.$$child.result)">{{$$child.$$child.result.Name}}</div> - person Suren Aznauryan; 04.12.2014

Бих искал ng-click [в директивата] да извика функцията selectResult в обхвата на контролера...

  1. За да прехвърлите функции (или свойства) в изолиран обхват, вие използвате атрибути в тага на директивата.

... но резултатният обект да идва от директивата [scope].

  1. Ако искате съдържанието на таг на директива да има достъп до обхвата на директивата, НЕ използвайте transclude. Указването на transclude: true казва на angular NOT да позволява на съдържанието на маркер на директива да има достъп до обхвата на директивата - обратното на това, което искате.

За да постигнете #1, можете да накарате потребителя да посочи шаблона по следния начин:

  <div ng-controller="MainCtrl">

    <search external-func='selectResult'>
      <div class="someStyle" ng-click="selectResult(result)">{{result.Name}}</div>
    </search>

  </div>

Имайте предвид, че потребителят трябва да добави допълнителен атрибут към маркера <search>. И все пак, този html може да се съчетае по-добре с философията на angular, че html трябва да дава подсказки на разработчика за това какъв javascript ще работи върху елементите.

След това определяте обхвата на изолацията по следния начин:

      scope: {
        selectResult: '=externalFunc'
      },

За да постигнете #2, не посочвайте transclude: true в директивата:

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

app.controller('MainCtrl', ['$scope', function($scope) {

  $scope.selectResult = function(result) {
    console.log("In MainCtrl: " + result.Name);
  };

}]);

app.controller('DirectiveCtrl', ['$scope', function($scope) {

  $scope.results = [ 
    {Name: "Mr. Result"},
    {Name: "Mrs. Result"}
  ]

}]);

app.directive('search', function() {

  return {
      restrict: 'E',

      scope: {
        selectResult: '=externalFunc'
      },

      template: function(element, attrs) {
      //                    ^       ^
      //                    |       |
      //    directive tag --+       +-- directive tag's attributes

        var inner_div = element.children();
        inner_div.attr('ng-repeat', 'result in results')

        //console.log("Inside template func: " + element.html());

        return element.html();  //Must return a string.  The return value replaces the innerHTML of the directive tag.
      },

      controller: 'DirectiveCtrl'
  }

}]);

HTML може да осигури още по-добър запис на това, което прави javascript, ако накарате потребителя да посочи своя шаблон по-подробно:

<search external-func='selectResult'>
  <div class="someStyle" 
    ng-click="selectResult(result)"
    ng-repeat="result in results">{{result.Name}}
  </div>
</search>

Но ако настоявате за минималистичния html:

<search>
  <div class="someStyle" ng-click="selectResult(result)">{{result.Name}}</div>
</search>

... след това можете динамично да добавите атрибута ng-repeat (както е показано по-горе), а също така е възможно динамично да картографирате външна функция към изолирания обхват:

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

app.controller('MainCtrl', ['$scope', function($scope) {

  $scope.selectDog = function(result) {
    console.log("In MainCtrl: you clicked " + result.Name);
  };

  $scope.greet = function(result) {
    console.log('MainCtrl: ' + result.Name);
  };

}]);

app.controller('DirectiveCtrl', ['$scope', function($scope) {

  $scope.results = [ 
    {Name: "Mr. Result"},
    {Name: "Mrs. Result"}
  ]

}]);

app.directive('search', function() {

  return {
    restrict: 'E',

    scope: {
      externalFunc: '&externalFunc'  //Cannot write => externalFunc: '&'
    },                               //because the attribute name is
                                     //'external-func', which means
                                     //the left hand side would have to be external-func.
    template: function(element, attrs) {
      //Retrieve function specified by ng-click:
      var inner_div = element.children();
      var ng_click_val = inner_div.attr('ng-click'); //==>"selectResult(result)"

      //Add the outer_scope<==>inner_scope mapping to the directive tag:
      //element.attr('external', ng_click_val); //=> No worky! Angular does not create the mapping.
      //But this works:
      attrs.$set('externalFunc', ng_click_val) //=> external-func="selectResult(result)"
      //attrs.$set('external-func', ng_click_val); //=> No worky!

      //Change ng-click val to use the correct call format:
      var func_args = ng_click_val.substring(ng_click_val.indexOf('(')); //=> (result)
      func_args =  func_args.replace(/[\(]([^\)]*)[\)]/, "({$1: $1})"); //=> ({result: result})
      inner_div.attr('ng-click', 'externalFunc' + func_args); //=> ng-click="externalFunc({result: result})"

      //Dynamically add an ng-repeat attribute:
      inner_div.attr('ng-repeat', 'result in results')

      console.log("Template: " + element[0].outerHTML);
      return element.html();
    },

    controller: 'DirectiveCtrl'
  }
})

Ако искате да извикате външната функция с повече от един аргумент, можете да направите следното:

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

app.controller('MainCtrl', ['$scope', function($scope) {

  $scope.selectResult = function(result, index) {
    console.log("In MainCtrl: you clicked " 
                 +  result.Name 
                 + " " 
                 + index);
  };

}]);

app.controller('DirectiveCtrl', ['$scope', function($scope) {

  $scope.results = [ 
    {Name: "Mr. Result"},
    {Name: "Mrs. Result"}
  ]

}]);

app.directive('search', function() {
  return {
    restrict: 'E',

    scope: {
      external: '='
    },

    template: function(element, attrs) {
      //Extract function name specified by ng-click:
      var inner_div = element.children();
      var ng_click_val = inner_div.attr('ng-click'); //=>"selectResult(result, $index)"
      var external_func_name =  ng_click_val.substring(0, ng_click_val.indexOf('(') ); //=> selectResult
      external_func_name = external_func_name.trim();

      //Add the outer_scope<==>inner_scope mapping to the directive tag:
      //element.attr('externalFunc', ng_click_val); => No worky!
      attrs.$set('external', external_func_name);  //=> external="selectResult"

      //Change name of ng-click function to 'external':
      ng_click_val = ng_click_val.replace(/[^(]+/, 'external');
      inner_div.attr('ng-click', ng_click_val);

      //Dynamically add ng-repeat to div:
      inner_div.attr('ng-repeat', 'result in results');

      console.log("Template: " + element[0].outerHTML);
      return element.html();
    },

    controller: 'DirectiveCtrl'
  }
});
person 7stud    schedule 20.12.2014
comment
Това, което не ми харесва в този подход е, че директивата трябва да има познания за изпълнението на шаблона, за да работи. Ами ако имаше две различни функции, които искам да извикам? Не трябва да променям директивата, за да направя това. Може би има начин да прехвърлите самия обхват в директивата, така че всяка функция да може да бъде извикана от шаблона? - person adam0101; 22.12.2014
comment

Използвам BO 4.2 + SQL Server 2008 за отчитане. Режимът на проектиране за отчет позволява използване на класиране на данни във филтри за заявка, включително процентно класиране. За процентно класиране BO извиква функцията PERCENT_RANK() в SQL Server. Изглежда, че съобщението за грешка „PERCENT_RANK“ не е разпознато име на вградена функция“. Знам, че тази функция е въведена в SQL Server 2012. Първият ми въпрос е - защо BO Designer позволява добавяне на процентен ранг към заявката и този бутон не е деактивиран/скрит при работа със SQL Server 2008? Вторият въпрос е - какви начини да деактивирате/скриете този бутон?

p.s. Можете да намерите този бутон по пътя: -> Дизайн на отчета -> Редактиране на доставчик на данни -> Панел за филтриране на заявки -> Добавяне на класиране на базата данни -> задаване на '% отгоре' (Вижте екрана)
p.p.s Използвайки BO 3.1, този бутон е деактивиран в моя случай и е ОК, но трябва да го поправя за BO 4.2.

въведете описание на изображението тук

Благодаря ти

- person 7stud; 23.12.2014
comment
Но вие сте го кодирали само за една функция и тя трябва да бъде ng-click. Какво ще стане, ако искам една функция да избера и друга функция, която показва повече подробности при преместване на мишката? Трябваше да променя директивата. Но мисля, че си на път за нещо. Ако предадох external обект, който капсулира всички функции, които бих искал да извикам, не мога ли да стигна до тези функции в шаблона, като направя ng-click="external.mySelectFunction()"? - person adam0101; 23.12.2014
comment
Но сте го кодирали твърдо само за една функция. Не. Шаблонът (т.е. ‹div›) може да посочи произволно име на функция и след това съответната функция ще бъде извикана във външния контролер. и трябва да е ng-click. Можете ли да посочите нещо, което сте публикували, което е заявило (или подразбиращо), че искате име на функция, НЕпосочено от ng-click, да бъде извикано във външния обхват? - person 7stud; 23.12.2014
comment
Не, директивата, както сте я написали, може да приеме само една функция. Ами ако искам 6 различни бутона в шаблона? Не трябва да заявявам или намеквам, че една директива не трябва да се нуждае от изрично знание за това как потребителят на тази директива ще приложи шаблона. Приетият отговор няма това ограничение и мисля, че вашият отговор също няма да го има, ако промените external да бъде обект, който капсулира всяка възможна функция, която шаблонът може да иска да извика. - person adam0101; 23.12.2014