宣言されている要素により多くのディレクティブを追加するを考慮したディレクティブを作成しようとしています。たとえば、datepicker
、datepicker-language
、およびng-required="true"
を追加するように注意するディレクティブを作成したいと思います。
これらの属性を追加してから$compile
を使用しようとすると、明らかに無限ループが発生するので、必要な属性を追加したかどうかを確認しています。
angular.module('app')
.directive('superDirective', function ($compile, $injector) {
return {
restrict: 'A',
replace: true,
link: function compile(scope, element, attrs) {
if (element.attr('datepicker')) { // check
return;
}
element.attr('datepicker', 'someValue');
element.attr('datepicker-language', 'en');
// some more
$compile(element)(scope);
}
};
});
もちろん、要素を$compile
しないと、属性は設定されますが、ディレクティブはブートストラップされません。
このアプローチは正しいですか、それとも間違っていますか?同じ動作を実現するためのより良い方法はありますか?
UDPATE:$compile
がこれを達成する唯一の方法であるという事実を考えれば、最初のコンパイルパスをスキップする方法はありますか(要素に複数の子が含まれる可能性があります)。たぶんterminal:true
を設定することによって?
PDATE 2:私はディレクティブをselect
要素に入れようとしましたが、予想通り、コンパイルは2回実行されます。つまり、予想されるoption
の数は2倍になります。
1つのDOM要素に複数のディレクティブがあり、それらが適用される順序が重要な場合は、priority
プロパティを使用してアプリケーションを順序付けることができます。大きい数字が最初に実行されます。指定しない場合、デフォルトの優先度は0です。
EDIT:議論の後、ここに完全な実用的なソリューションがあります。キーは、属性を削除:element.removeAttr("common-things");
、およびelement.removeAttr("data-common-things");
(ユーザーがhtmlでdata-common-things
を指定した場合)
angular.module('app')
.directive('commonThings', function ($compile) {
return {
restrict: 'A',
replace: false,
terminal: true, //this setting is important, see explanation below
priority: 1000, //this setting is important, see explanation below
compile: function compile(element, attrs) {
element.attr('tooltip', '{{dt()}}');
element.attr('tooltip-placement', 'bottom');
element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html
return {
pre: function preLink(scope, iElement, iAttrs, controller) { },
post: function postLink(scope, iElement, iAttrs, controller) {
$compile(iElement)(scope);
}
};
}
};
});
ワーキングプランカーは次の場所にあります: http://plnkr.co/edit/Q13bUt?p=preview
または:
angular.module('app')
.directive('commonThings', function ($compile) {
return {
restrict: 'A',
replace: false,
terminal: true,
priority: 1000,
link: function link(scope,element, attrs) {
element.attr('tooltip', '{{dt()}}');
element.attr('tooltip-placement', 'bottom');
element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html
$compile(element)(scope);
}
};
});
terminal: true
およびpriority: 1000
(高い数値)を設定する必要がある理由:
DOMの準備ができたら、angularはDOMを歩いて、登録されているすべてのディレクティブを識別し、priority
に基づいてディレクティブを1つずつコンパイルしますこれらのディレクティブが同じ要素にある場合 。カスタムディレクティブの優先度を高い数値に設定して、コンパイルされるようにしますfirst、terminal: true
を使用すると、このディレクティブの後に他のディレクティブはskippedになります。コンパイル済み。
カスタムディレクティブがコンパイルされると、ディレクティブを追加してそれ自体を削除することで要素を変更し、$ compileサービスを使用してすべてのディレクティブ(スキップされたものを含む)をコンパイルします。
terminal:true
とpriority: 1000
を設定しないと、一部のディレクティブがコンパイルされる可能性がありますbeforeカスタムディレクティブ。そして、カスタムディレクティブが要素をコンパイルするために$ compileを使用する場合は、既にコンパイル済みのディレクティブを再度コンパイルします。これは、カスタムディレクティブの前にコンパイルされたディレクティブがすでにDOMを変換している場合は特に、予期しない動作を引き起こします。
優先度と端末の詳細については、 ディレクティブの「端末」を理解する方法?
テンプレートも変更するディレクティブの例は、ng-repeat
(優先度= 1000)です。ng-repeat
がコンパイルされると、ng-repeat
が前にテンプレート要素のコピーを作成します他のディレクティブが適用されます。
@Izhakiのコメントのおかげで、ここにngRepeat
ソースコードへの参照があります。 https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js
単純なテンプレートタグを使用するだけで、これらすべてを実際に処理できます。例として http://jsfiddle.net/m4ve9/ を参照してください。私は実際にはスーパーディレクティブ定義のコンパイルまたはリンクプロパティを必要としなかったことに注意してください。
コンパイルプロセスの間、Angularはコンパイルの前にテンプレートの値を取得するので、そこにさらにディレクティブを追加することができ、Angularはそれを代行します。
これが元の内部コンテンツを保持する必要があるスーパーディレクティブである場合は、transclude : true
を使用して内部を<ng-transclude></ng-transclude>
に置き換えることができます。
それが助けになることを願って、何かが明確でないかどうか私に知らせて
アレックス
動的に追加する必要があるディレクティブをビューに移動し、さらにオプションの(基本)条件付きロジックを追加するソリューションがあります。これにより、ハードコーディングされたロジックなしでディレクティブがクリーンに保たれます。
ディレクティブはオブジェクトの配列を取り、各オブジェクトは追加されるディレクティブの名前とそれに渡す値(もしあれば)を含みます。
私はこのようなディレクティブのユースケースを考えるのに苦労していました(ある条件に基づいてディレクティブを追加するだけの条件付きロジックを追加するのが役に立つかもしれないと思うまで(下の答えはまだ考えられませんが))。ディレクティブを追加するかどうかを決定するブール値、式、または関数(たとえば、コントローラで定義されている)を含む必要があるif
プロパティを追加しました。
私はattrs.$attr.dynamicDirectives
を使用して、チェックする文字列値をハードコーディングせずにディレクティブを追加するために使用される正確な属性宣言(例:data-dynamic-directive
、dynamic-directive
)を取得します。
angular.module('plunker', ['ui.bootstrap'])
.controller('DatepickerDemoCtrl', ['$scope',
function($scope) {
$scope.dt = function() {
return new Date();
};
$scope.selects = [1, 2, 3, 4];
$scope.el = 2;
// For use with our dynamic-directive
$scope.selectIsRequired = true;
$scope.addTooltip = function() {
return true;
};
}
])
.directive('dynamicDirectives', ['$compile',
function($compile) {
var addDirectiveToElement = function(scope, element, dir) {
var propName;
if (dir.if) {
propName = Object.keys(dir)[1];
var addDirective = scope.$eval(dir.if);
if (addDirective) {
element.attr(propName, dir[propName]);
}
} else { // No condition, just add directive
propName = Object.keys(dir)[0];
element.attr(propName, dir[propName]);
}
};
var linker = function(scope, element, attrs) {
var directives = scope.$eval(attrs.dynamicDirectives);
if (!directives || !angular.isArray(directives)) {
return $compile(element)(scope);
}
// Add all directives in the array
angular.forEach(directives, function(dir){
addDirectiveToElement(scope, element, dir);
});
// Remove attribute used to add this directive
element.removeAttr(attrs.$attr.dynamicDirectives);
// Compile element to run other directives
$compile(element)(scope);
};
return {
priority: 1001, // Run before other directives e.g. ng-repeat
terminal: true, // Stop other directives running
link: linker
};
}
]);
<!doctype html>
<html ng-app="plunker">
<head>
<script src="//code.angularjs.org/1.2.20/angular.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
<script src="example.js"></script>
<link href="//netdna.bootstrapcdn.com/Twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
</head>
<body>
<div data-ng-controller="DatepickerDemoCtrl">
<select data-ng-options="s for s in selects" data-ng-model="el"
data-dynamic-directives="[
{ 'if' : 'selectIsRequired', 'ng-required' : '{{selectIsRequired}}' },
{ 'tooltip-placement' : 'bottom' },
{ 'if' : 'addTooltip()', 'tooltip' : '{{ dt() }}' }
]">
<option value=""></option>
</select>
</div>
</body>
</html>
受け入れられたものは私のためには全くうまくいきませんでしたので私は私のソリューションを追加したいと思いました。
ディレクティブを追加する必要がありましたが、その要素についても注意を払います。
この例では、要素に単純なng-styleディレクティブを追加しています。無限のコンパイルループを防ぎ、ディレクティブを維持できるようにするために、要素を再コンパイルする前に、追加したものが存在するかどうかを確認するチェックを追加しました。
angular.module('some.directive', [])
.directive('someDirective', ['$compile',function($compile){
return {
priority: 1001,
controller: ['$scope', '$element', '$attrs', '$transclude' ,function($scope, $element, $attrs, $transclude) {
// controller code here
}],
compile: function(element, attributes){
var compile = false;
//check to see if the target directive was already added
if(!element.attr('ng-style')){
//add the target directive
element.attr('ng-style', "{'width':'200px'}");
compile = true;
}
return {
pre: function preLink(scope, iElement, iAttrs, controller) { },
post: function postLink(scope, iElement, iAttrs, controller) {
if(compile){
$compile(iElement)(scope);
}
}
};
}
};
}]);
superDirectiveStatus="true"
のように、要素自体の属性に状態を保存してみてください。
例えば:
angular.module('app')
.directive('superDirective', function ($compile, $injector) {
return {
restrict: 'A',
replace: true,
link: function compile(scope, element, attrs) {
if (element.attr('datepicker')) { // check
return;
}
var status = element.attr('superDirectiveStatus');
if( status !== "true" ){
element.attr('datepicker', 'someValue');
element.attr('datepicker-language', 'en');
// some more
element.attr('superDirectiveStatus','true');
$compile(element)(scope);
}
}
};
});
これがお役に立てば幸いです。
1.3.xから1.4.xへの変更がありました。
Angular 1.3.xではこれがうまくいきました:
var dir: ng.IDirective = {
restrict: "A",
require: ["select", "ngModel"],
compile: compile,
};
function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
tElement.append("<option value=''>--- Kein ---</option>");
return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {
attributes["ngOptions"] = "a.ID as a.Bezeichnung for a in akademischetitel";
scope.akademischetitel = AkademischerTitel.query();
}
}
Angular 1.4.xでは、これを行う必要があります。
var dir: ng.IDirective = {
restrict: "A",
compile: compile,
terminal: true,
priority: 10,
};
function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
tElement.append("<option value=''>--- Kein ---</option>");
tElement.removeAttr("tq-akademischer-titel-select");
tElement.attr("ng-options", "a.ID as a.Bezeichnung for a in akademischetitel");
return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {
$compile(element)(scope);
scope.akademischetitel = AkademischerTitel.query();
}
}
(受け入れられた答えから: https://stackoverflow.com/a/19228302/605586 Khanh TOから)。
場合によってはうまくいく可能性がある簡単な解決策は、ラッパーを作成して$ compileしてから、それに元の要素を追加することです。
何かのようなもの...
link: function(scope, elem, attr){
var wrapper = angular.element('<div tooltip></div>');
elem.before(wrapper);
$compile(wrapper)(scope);
wrapper.append(elem);
}
この解決策には、元の要素を再コンパイルしないことで物事を単純に保つという利点があります。
これは、追加されたディレクティブのrequire
のいずれかがオリジナルのエレメントのディレクティブのいずれかである場合、またはオリジナルのエレメントが絶対位置にある場合には機能しません。