It seems like I get confused by isolated scopes in directives and hope you can help me out.
I tried to wrap a piece of code (which contains some custom directives) into a new directive to reduce code duplication. Obviously I needed to give some attributes like ng-model into my new directive as a parameter to make the directive reusable. ng-model does not like expressions though (I tried ng-model="{{myVariableWhichContainsDesiredNgModelString}}" at first) and thus I ended up at this article: AngularJS - Create a directive that uses ng-model.
While the accepted answer seems to work for a simple setup, I edited the plunker from the accepted answer to test out if it would work with nested directives as well: (in my app I need to wrap directives from a third party-library which I can not edit) Plunker. In my code each directive seems to generate its own scope and two-way-databinding by using = in the scope definition does not seem to work out as desired.
EDIT: Since it was not clear what i am asking I edited the Plunker above and will rephrase the question: In the Plunker I have three input-fields which are supposed to bind to the same model-value. This works initially, but as soon as I edit the third input-field it generates its own variable in its isolated scope instead of updating the initial value. Obviously the third input field refers to the new variable from that point on. How can I avoid that behaviour and keep the input linked to $scope.model.name?
Observation: removing the isolated-scope-directive from the template makes everything work as expected...
template: '<div><my-input ng-model="myDirectiveVar"></my-input></div>',
instead of
template: '<div><my-isolated-scope-directive><my-input ng-model="myDirectiveVar"></my-input></my-isolated-scope-directive></div>',
Plunker
HTML:
<!-- this binds to the model which i would like all my inputs to bind to.-->
<input ng-model="name">
<!-- Example 1: This seems to generate a new modelvalue in the isolated-scope directive. Can I avoid this without modifying that directive?-->
<my-isolated-scope-directive><my-input ng-model="name"></my-input></my-isolated-scope-directive>
<!-- Example 2: This is what i would like my code to look like in the end: One directive which uses the code-snippet of Example 1 as template and passes some parameters into that template.-->
<my-wrapper-directive my-directive-var="name"></my-wrapper-directive>
Directives:
my-input contains a modified input-field:
app.directive('myInput', function() {
return {
restrict: 'E',
replace: true,
require: 'ngModel',
template: '<input class="some">',
link: function($scope, elem, attr, ctrl) {
console.debug($scope);
}
};
})
my-isolated-scope-directive is a placeholder-directive with its own isolated scope to simulate the behaviour for nested directives:
.directive('myIsolatedScopeDirective', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
scope: {
something: '='
},
template: '<div ng-transclude></div>',
link: function($scope, elem, attr, ctrl) {
console.debug($scope);
}
};
})
my-wrapper-directive encapsulates both previous directives and accepts a parameter which should be used as ng-model value of the input field:
.directive('myWrapperDirective', function() {
return {
restrict: 'E',
transclude: false,
replace: true,
scope: {
myDirectiveVar: '='
},
template: '<div><my-isolated-scope-directive><my-input ng-model="myDirectiveVar"></my-input></my-isolated-scope-directive></div>',
link: function($scope, elem, attr, ctrl) {
console.debug($scope);
}
};
});
Any suggestions and hints on what I am missing are appreciated. Can I maybe somehow link ng-model to a service-instance without making my directive dependant on that service?
I wouldn't do it like this as it is old notation using scopes... I would use controllerAs and bindToController
script:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function() {
this.model = { name: 'World' };
this.name = "Felipe";
});
app.directive('myInput', function() {
return {
restrict: 'E',
replace: true,
// controllerAs: 'app',
require: 'ngModel',
template: '<input class="some">',
controller: function(){
}
};
})
.directive('myIsolatedScopeDirective', function() {
return {
restrict: 'E',
transclude: true,
controllerAs: 'app1',
bindToController: {
something: '='
},
template: '<div ng-transclude></div>',
controller: function(){
}
};
})
.directive('myWrapperDirective', function() {
return {
restrict: 'E',
transclude: false,
controllerAs: 'app2',
bindToController: {
myDirectiveVar: '='
},
template: '<div><my-isolated-scope-directive>'+
'<my-input ng-model="app2.myDirectiveVar"></my-input>'+
'</my-isolated-scope-directive></div>',
controller: function(){
}
};
});
index:
<!doctype html>
<html ng-app="plunker" >
<head>
<meta charset="utf-8">
<title>AngularJS Plunker</title>
<link rel="stylesheet" href="style.css">
<script>document.write("<base href=\"" + document.location + "\" />");</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl as main">
This scope value
<input ng-model="main.model.name">
<my-isolated-scope-directive>
<my-input ng-model="main.model.name"></my-input>
</my-isolated-scope-directive>
<my-wrapper-directive my-directive-var="main.model.name">
</my-wrapper-directive>
</body>
</html>
See the plunker:
http://plnkr.co/edit/VD0wXO1jivQc3JvfQFTh?p=preview
UPDATE
yes, good point, so if you want to use controllerAs, you need angular 1.2 as minimum, for bindToController you need angular 1.3
Related
Hi i have created a directive and it does not pass back the correct message.
The directive is used to pass tool-tips back to the html page
this is what the html looks like
<info-text info-msg="Adding another applicant might help you to get approved."></info-text>
below is the directive
(function(){
angular.module('mainApp').directive('infoText', [function () {
return {
scope: { infoMessage: '&infoMsg' },
restrict: 'E',
replace: true,
template: '<p class="info-text"><i class="fa fa-info-circle"></i> {{infoText}}</p>',
link: function(scope, elem, attrs) {
$(elem).prev().hover(function(){
$(elem).addClass('info-hover');
}, function(){
$(elem).removeClass('info-hover');
});
}
};
}]);
}());
the message i get rendered on the page is as follows (it does send the glyphicon):
{{infoText}}
Any ideas,
thanks. Kieran.
You should not use & for this sort of binding, basically it is used for expression binding. I think one way binding (#) is efficient for what you are doing.
Also you should change directive template {{infoText}} to {{infoMessage}}
Markup
<info-text
info-msg="{{'Adding another applicant might help you to get approved.'}}"></info-text>
Directive
angular.module('mainApp').directive('infoText', [function () {
return {
scope: { infoMessage: '#infoMsg' },
restrict: 'E',
replace: true,
template: '<p class="info-text"><i class="fa fa-info-circle"></i> {{infoMessage}}</p>',
link: function(scope, elem, attrs) {
$(elem).prev().hover(function(){
$(elem).addClass('info-hover');
}, function(){
$(elem).removeClass('info-hover');
});
}
};
}]);
And making more cleaner and readable html you could place that string into some scope variable and pass that scope variable in info-msg attribute
Inside angularJS directive I'm trying to iterate over array and based on values I would like to create nested list of directives.
Current version of directive
Directive type
.directive("type", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "<div></div>",
link: function(scope, element, attrs){
if (angular.isArray(scope.type)){
angular.forEach(scope.type, function(value, index){
$log.error(value);
element.append("<type type='scope.type['"+index+"]'></type>");
});
} else if (angular.isObject(scope.type)){
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
};
})
I also tried to use above directive with next version of link function:
if (angular.isArray(scope.type)) {
element.append("<div ng-repeat='element in scope.type'><type type='element'></type></div>");
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
None of provided codes solve my issue.
Below you will find example explaining on specific case:
Let's say that I had next object in the scope.type = [null,"int"]. Now I would like to use <type type='type'><type> and as a result of first evaluation I want to have sth like:
<type type='type[0]'></type><type type='type[1]'></type>
Further evaluation of those values should lead to some simpler form but right now it is not important.
How I can achieve sth like this?
Edit
I tried even to exctract part of the code responsible for iteration to the seperate directive but it still does not work. Code:
Update link function in type directive:
link: function(scope, element, attrs) {
if (angular.isArray(scope.type)) {
element.append("<typeb type='scope.type'></typeb>")
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
New directive:
.directive("typeb", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "<div ng-repeat='t in type'>{{t}}</div>",
};
})
Problem still occurs but generated html contains only next pieces as a result of typeb directive:
<!-- ngRepeat: t in type -->
The problem you are getting is <!-- ngRepeat: t in type --> this is because your type didn't contains any value, when it is inside typeb directive
Your directive shouldn't be use scope.variable on view.
Scope variable will be directly accessible by their name like
{{type}} or <typeb type='type'></typeb>
Change your link code to below.
Directive link
link: function(scope, element, attrs) {
if (angular.isArray(scope.type)) {
element.append("<typeb type='type'></typeb>")
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{type}}</div>")
}
$compile(element.contents())(scope)
}
Thanks.
Use an ng-repeat in the template
<your-directive attr="item.someattr" ng-repeat="item in items"></your-directive>
.directive("type", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "NG REPEAT HERE",
...
})
I'm trying to pass an already dynamic value to my directive to fetch a templateUrl for this value. Let me explain with some Sourecode:
<p ng:repeat="cell in field.Cells">
<cell-element handler="{{cell.handler}}"/>
<!-- cell.handler is e.g. "User" -->
</p>
myapp.directive('cellElement', function() {
return {
restrict: 'E',
templateUrl: function (tElement, tAttrs, $compile) {
return '/ajax/' + tAttrs.handler == undefined ? 'foo' : tAttrs.handler +'.html';
},
}
});
Unfortunately, the value of tAttrs.handler is always the literal expression "{{cell.handler}}" instead of the respective value. I tried so many different ways - any guess?
UPDATE:
myapp.directive('cellElement', function() {
return {
restrict: 'E',
scope: { handler: '=handler' },
template: '<ng-include src="\'/ajax/\' + handler"></ng-include>'
}
});
As workaround i used another approach that works. But i would prefer the initial way by using the templateUrl function as i e.g. want to check if "handler" is a valid value.
I think that this alternative can work:
myapp.directive('cellElement', function() {
return {
restrict: 'E',
template: '<div data-ng-include="templateUrl"></div>',
link: function ($scope, iElement, attr) {
$scope.templateUrl= $scope[attr.handler];
}
}
And you have to cosume without backets:
<p ng:repeat="cell in field.Cells">
<cell-element handler="cell.handler"/>
<!-- cell.handler is e.g. "User" -->
</p>
The thing is that probably the template is not compiled, so the value you want is not yet interpolated. If you tried to retrieve it in the link function, it would work.
Updating as there is some confusion as to what I am asking. I would like to use a directive to inject a variable into the controller used by that directive. I realize I can use the $scope for that, but I don't find that an intuitive solution.
Essentially I want my controller to have the proposal variable injected into it.
My intended usage:
<blah-directive proposal="proposal"></blah-directive>
The directive (so far):
app.directive('blahDirective', function () {
return {
restrict: 'E'
, transclude: true
, replace: true
, scope: {
proposal: '='
}
, templateUrl: 'blahTemp.html'
, controller: blahController
};
});
blahTemp.html
<form class="form-horizontal" role="form" name="myBidForm">
**{{ proposal }}**
</form>
this is displaying the value proposal variable in the $scope fine, but it is not what I want. Essentially I would like to define my controller like:
var blahController = function($scope, SomeOtherResource, proposal) {
}
If you want to inject locals into a controller use $controller.
Here is an example (plunker):
app.directive('blahDirective', function ($controller) {
return {
restrict: 'E',
scope: {
proposal : "="
},
transclude: true,
replace: true,
templateUrl: 'blahTemp.html',
link : function (scope, elm, attrs){
scope.proposal = {};
var locals = {
$scope: scope ,
proposal: scope.proposal
};
$controller('blahController', locals);
}
};
});
New to Angular, just trying to get some harmony with Zurb Foundation 4. A case in point; I am trying to make use of the http://foundation.zurb.com/docs/components/reveal.html component.
Straight-forward approach seemed to be to wrap as directives:
directive('modal', function() {
return {
template: '<div ng-transclude id="notice" class="reveal-modal">' +
'<a close-modal></a>' +
'</div>',
restrict: 'E',
transclude: true,
replace: true,
scope: {
'done': '#',
},
transclude: true,
link: function(SCOPE, element, attrs, ctrl) {
SCOPE.$watch('done', function (a) {
// close-modal
});
}
}
}).
directive('closeModal', function() {
return {
template: '<a ng-transclude href="#" class="close-reveal-modal">x</a>',
restrict: 'A',
transclude: true,
replace: true
}
}).
directive('showModal', function() {
return {
template: '<a ng-transclude class="reveal-link" data-reveal-id="notice" href="#"></a>',
restrict: 'A',
transclude: true,
replace: true,
}
});
This works fine up to a point, for example, I can use the modal to show different notices from a template:
<modal done="">
<div ng-include src="'partials/notices/' + notice + '.html'"></div>
</modal>
<select ng-model="notice" ng-options="n for n in ['notice-1', 'notice-2']">
<option value="">(blank)</option>
</select>
<a show-modal>show modal</a>
However, where it gets sticky is if I want to trigger close-modal/ show-modal from a controller/ on a certain event (e.g. within $watch). I'm assuming my directive needs a controller to trigger click, but would be good Angular practise?
This question is very old and I don't know if it works with Reveal. But I've wrapped the dropbox library into Angular only by calling the .foundation() method on the .run() method of angular:
app.run(function ($rootScope) {
$rootScope.$on('$viewContentLoaded', function () {
$(document).foundation();
});
});
That works for me. I guess that you can also create a directive to handle user interaction.
Controllers should not be triggering UI events directly neither manipulate UI elements directly. All that code is supposed to go in Directives.
What you can do is:
Bind a bool in Directive Scope to the Parent scope and add a watch on that. I think you already did that. OR
Do a scope.$broadcast on the controller and add scope.$watch on the directive to close then.