I have created a plunker in order to emphasize the problem:
http://plnkr.co/edit/QHUpCv?p=preview
If I remove the custom attribute, or move the ng-select out of it, the companies are listed as the should, in case I use ng select with or within the custom attribute directive that I have created it breaks.
I suspect that some kind of $watch is required there inside of the scope for menuCtrl, but I have no idea whatsoever how to implement it.
As far as i can tell, the problem is that you are generating a new scope for your directive, so a quick fix would be to forbid that via:
// [...]
restrict: "A",
scope: false,
link: //..
I made a plunkr here to illustrate.
If you do want it this way and prefer an own scope for this directive, you can pass in the values for the select, i.e.
<div restrict companies="companies" access="admin">
and read it in in the scopeof the directive:
restrict: 'A',
prioriry: 100000,
scope: {
companies: '='
},
of course, you would then use the companies directly with the select:
<select ng-model="data.selectedCompany" ng-options="company for (id, company) in companies">
Related
When building angular directives, I've found there is more than one way to pass a value from the parent scope to the child scope. Some ways I'm aware of are:
Don't use an isolate scope at all, and the child will simply have
access to the parent scope (you can mount a pretty good argument that this is bad).
Use the attributes parameter of a link function.
Use an isolate scope and bind to the attribute (e.g. param: '=')
The codepen here: https://codepen.io/ariscol/pen/WEKzMe shows two similar directives, one done with link and one done with 2-way binding in an isolate scope. Furthermore, it shows how they differ as far as 1-time binding compared to 2-way binding. For reference, here are the two directives:
app.directive("contactWidgetWithScope", function() {
return {
restrict: 'E',
template: '<div ng-bind="contact.name"></div>'
+ '<div ng-bind="contact.title"></div>'
+ '<div ng-bind="contact.phone"></div>',
scope: {
contact: '='
}
};
});
app.directive("contactWidgetWithLink", function() {
return {
restrict: 'E',
template: '<div ng-bind="name"></div>'
+ '<div ng-bind="title"></div>'
+ '<div ng-bind="phone"></div>',
scope: {},
link: function(scope, elem, attrs) {
scope.name = attrs.contactname;
scope.title = attrs.contacttitle;
scope.phone = attrs.contactphone;
}
};
});
Now, if I were trying to decide which way was "better", I might consider how I was going to use this directive. If I was going to have a thousand contacts, and I wanted to use this directive to list all one thousand contacts on a page, in an ng-repeat, for example, I imagine that I would have significantly better performance with link, as it won't add any watchers. On the other hand, if I wanted this directive to be incorporated into a page header, and I wanted the contact details to be updated as you clicked on any given contact in a list, I would want 2-way binding, so that any change to some "selectedContact" property in a parent scope would be automatically reflected in this directive. Are those the proper considerations? Are there others?
To add to my confusion, it is simple to add an observer to a linked attribute and achieve a 1-way binding such that a change in the value of the attribute will be reflected in the child. Would doing this have more or less of a performance impact? Conversely, I imagine you could do a 1-time binding on the value of the scope version and thereby eliminate the performance impact, e.g.: <contact-widget-with-scope contact="::vm.contact">. That should work, right? Seems like that option gives you a lot of flexibility, because it means the person who invokes the directive can decide if they want to pay the performance price to get the benefit of 2-way binding or not. Are these considerations accurate? Are there other things I ought to consider when deciding how to make values available to my directives?
I am fairly new to Angular and still learning. I posted a question regarding how to pass scope data to directives. Two nice people answered my question but the explanations were very short, so I do not understand some of the code in two of the answers. I will post the two answers and accompanying code here, and if possible someone with be able to answer my questions.
1st set of Code
<li my-directive price="item.price" ng-repeat="item in products">{{item.name}} — {{item.price}}</li>
myApp.directive('myDirective', function(){
return {
scope: { price: '=' },
require: 'ngModel',
link : function(scope){
console.log(scope.price)
},
controller: function(scope, element, attrs, ngModel){
console.log(ngModel.price);
console.log(scope.price);
}
}
});
point 1
Why is scope declared in the directive ? Regarding 'isolated scope' property name has to be price, is there a reason for the variable name here? Can we give it a different name ?
point 2
I am new to ng. This is the first time I've noticed a 'controller declared in directive'. When and why do people declare a controller in a directive? Mostly we declare a controller outside of a directive. Are a controller function and a controller inside a directive both the same, or different?
point 3
What is the meaning of require: 'ngModel', ? If we do not write require: `ngModel what will fail to work?
2nd set of Code
angular.module('myApp')
.directive('myDirective', function () {
return {
transclude: true,
restrict: 'AEC',
scope: {
name: '=',
price: '='
},
templateUrl: 'my-directive.html',
link: function (scope, element, attr) {
}
}
}
});
<li my-directive ng-repeat="item in products" price="item.price" name = "item.name"></li>
points 1
What is transclude: true ? What is the meaning of transclude = true or false? In what kind of situation do people work using transclude: true or false?
points 2
Where I have to put file my-directive.html in folder, what would be the location of the file?
Will Angular load this template file automatically?
1st set
Point 1: why scope declared in directive ? isolated scope property name has to be price ? here can we give different name ?
Ans: scope is declared in directive so that each instance of directive will have their own scope independent of each other. No it need not be price, it can be any variable name as per your choice. It's a general practice to use variable names in context of directive's usage.
Point 2:
i am new in ng. this is first time i notice controller declared in directive. why and when people declare controller in directive ? mostly we declare controller out side of directive. controller function and controller inside in directive both are same or different?
Ans: This controller is the controller of your directive and is different than what you normally see for a module.
Point 3:
what is meaning of require: 'ngModel', ? if we do not write require: `ngModel then what will not work?
Ans: When using your directive, it forces it to be used along with the attribute/controller ng-model. You can refer this answer for more details.
2nd set
Point1: what is transclude: true ? what is the meaning of transclude = true or false? in what kind of situation people work with transclude: true or false?
Ans:It's setting transclude as true. As per documentation:
Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
transclude: true allows us to wrap a users template with our template. I've used it as true in a directive I wrote where if a table is empty, I show a error message showing No Data Available. In my case, table will not by displayed at all, instead this directive's template is displayed.
Points 2:
where i have to put the file my-directive.html in folder. what would be the location of the file?
angular will load this template file automatically?
Ans: You can place this template anywhere in the project folder. Like, in a folder named views or templates. As long as you provide correct path in templateUrl, angular will find the file and use it in the directive.
I'm trying to implement a double select list form component just to try out angular and try to understand it better.
The functionality of this directive is that the parent model contains the list of Available Items to choose from, and contains the destination list (selected) where chosen items are placed. When an item is chosen it will be removed from the available array and placed in the selected array.
As a form may require more than one of these double select list components I don't want to rely on hard coded property names but instead allow the lists to be passed in for two way data binding.
Here is a JSBin of the code: Double Select Lists
I haven't gone any further than just trying to bind the data to the lists via the passed in model property names.
On the directive, the following scope properties are defined. The availableItems and selectedItems should both be bound to the parent controllers properties. The highlightedAvailable and highlightedSelected are to store the user selections prior to the button clicks.
scope: {
availableItems: '=',
selectedItems: '=',
highlightedAvailable: [],
highlightedSelected: []
},
Can someone please tell me why the controller properties never bind to the directive properties?
Thanks!
First, you have an error being caused by your scope:
scope: {
availableItems: '=',
selectedItems: '=',
highlightedAvailable: [],
highlightedSelected: []
},
should be:
scope: {
availableItems: '=',
selectedItems: '='
},
Declare the arrays somewhere else, like in the link function:
link: function (scope, element, attrs) {
scope.highlightedAvailable = [];
scope.highlightedSelected = [];
The next problem was the way you specified the attributes to the directive, you had:
<div ng-Select-Lists availableItems='availableItems' selectedItems='selectedItems'>
Try this instead:
<div ng-Select-Lists available-items='availableItems' selected-items='selectedItems'>
To expand on aet's answer, the reason that the way you specified your directive attributes in your html did not work is because HTML is case-insensitive. So the 'availableItems' attribute was actually being passed to your directive scope as 'availableitems'. On the other hand, snake cased words like 'available-items' will be converted to camel case in your angular code.
That's the reason you write angular directives in the html as 'ng-repeat', 'ng-model', and so on, but in the angular source code you'll see these directive names camel cased: 'ngRepeat', 'ngModel'...
Be super careful to use snake-case in HTML, and camel case in your Javascript (Angular)! I've spent way too long on some bugs caused by that confusion.
I have the following set up as a directive in my angular project:
{
restrict: "AE",
replace: true,
template: template,
require: "ngModel",
scope: {
chosen: "=ngModel",
choices: "=choices",
placeholder: "#placeholder"
}
}
I have everything working internally for my directive, the missing piece right now is that when I select a value inside of it, the parent scope containing my directive isn't receiving any kind of update. Despite me making assigments to chosen from anywhere inside of my directive.
As the title states, what's the simplest way for me to assign a value chosen inside of my directive, to it's parent's scope?
Ideally I'd like the solution to:
Not require me to use the link function - I feel like this can be done declaratively
Not require my directive to guess anything about it's parent scope
As a followup question, is there any reason to use ngModel in this circumstance? Is it or could it be beneficial? Or could I just as easily get away with recycling a name attribute which contains the parent scope's desired return value?
I have a very strange phenomenon with a directive and an isolated scope, where the attributes in the scope work or do not work depending on the naming of the attribute. If I use
{check:'#check'}
it works just fine and as expected. However,if I use:
{checkN:'#checkN'}
the defined function never gets assigned. An example would look like:
HTML:
<item ng-repeat="list_item in model.list" model="list_item" checkN="checkName()" check="checkName()" position="$index"></item>'
Javascript
app.directive('item', function(){
return {
restrict: 'E',
replace : false,
scope:{
$index: '=position',
check: '&check',
checkN: '&checkN',
model:'='
},
template: '',
link: function(scope, element, attrs){
console.log(scope.check())
console.log(scope.checkN())
}
}
});
The console will then give me the following:
The checkName function has been called [which is the return string of the function]
undefined
It is really possible that it depends on the usage of capital letters? This would be very "unexpected" behaviour.
Thanks for your help
schacki
Html is case insensitive, therefore myAttribute and myattribute would be indistinguishable from each other depending on the browser. Angularjs' authors made a design decision about passing from html to javascript and vice-versa in terms of directives.
ngRepeat directive would be used as ng-repeat in the view(html).
Likewise, your directive checkN should be used as check-n for angular to recognise that as directive.