EDIT: It turns out that this actually works, it just doesn't show the content behind the 'model' attribute in the inspector. I didn't notice it because there was no content on that particular data point i was using. Facepalm thanks to all who helped.
So I'm trying to make my form structure simpler, by creating a pretty verbose nested directive structure. I'm using pretty basic angular code to achieve this, but for some reason, using an attribute from the parent as the value for a child directive's attribute doesn't work (code below to explain, had to change some proprietary words but its essentially the same). What am I doing wrong?
Parent HTML directive call:
<field-label project-for="projectName" project-model="data.product.projectName">Project Name</field-label>
Directive code:
app.directive("fieldLabel", function() {
return {
restrict: "E",
transclude: true,
scope: { model: '=projectModel', for: '#projectFor' },
templateUrl: 'views/products/label.html',
};
});
EDIT: By Request, the other directive in use here:
app.directive("projectOtherView", function() {
return {
restrict: "E",
scope: { field: '=projectOtherViewModel' },
templateUrl: 'views/products/XXX.html',
};
});
Template HTML
<div class="col-sm-2 text-right">
<label for="{{for}}" class="control-label" ng-transclude></label>
<project-other-view project-other-view-model="model"></project-other-view>
</div>
The 'for' works fine but the 'model' only passes through itself, not what it should be(the model name I passed through at the beginning).
As per my comment. The fact that it doesn't display anything other than what you've defined, in this case the word 'model' is fine, it is still wired up to the parent model. So if you were to put the expression {{field}} into your child template, you would get access to the two way binding in the model. So your model hierarchy looks something like this:
level 1: data.product.projectName (binding to next level: project-model="data.product.projectName")
level 2: projectModel (binding to next level: model: '=projectModel')
level 3: model (binding to next level: project-other-view-model="model")
level 4: projectOtherViewModel (binding to next level: field: '=projectOtherViewModel' )
Remember hyphens are removed and CamelCased so 'project-model' in a template becomes 'projectModel' in the javascript.
See http://plnkr.co/edit/MsAHkTU3KLXWhpplWAPs?p=preview for a working example.
Can you create a jsfiddle that replicates your problem? I have a feeling your problem is related to this
Related
I am working on a project that will have any number of HTML cards, and the format and positioning of the cards is the same, but the content of them will differ.
My plan was to implement the content of each card as an angular directive, and then using ngRepeat, loop through my array of directives which can be in any order, displaying each directive in a card. It would be something like this:
inside my controller:
$scope.cards = [
'directice-one',
'directive-two',
//etc..
]
my directive:
.directive('directiveOne', function () {
return {
restrict: "C",
template: '<h1>One!!</h1>'
};
})
.directive('directiveTwo', function () {
return {
restrict: "C",
template: '<h1>Two!!</h1>'
};
})
//etc..
my html:
<div class="card" ng-repeat="item in cards">
<div class="{{item}}"></div>
</div>
But.. the directives don't render. So I just get a div with class="directive-one".
This might be a design flaw on my part, some sort of syntax error, or just a limitation of angular. I'm not sure.
I've also considered making a directive <card>, and then passing the templateUrl: into it, but that would cause me to lose my access to $scope and the javsacript capabilities that I would have if each card was it's own directive.
So, advise, code help, anything would be very helpful!
I choose directives only when I need to use them in HTML mark up. For example, assuming cards layout is same and it takes different information based on user preference.
HTML File
<my-card Name="First" Option="Myoptions"></Card>
<my-card Name="Second" Option="GenOptions"></Card>
Directive
angular.module("testapp").directive("MyCard", function() {
scope: {
name: '#',
Option: '#'
Controller: "myCardController",
templateURL: "~/myCard/myCardTemplate.html"
}
});
In Template you can implement the information passed from HTML page via the directive.
Hope this helps.
Do take note that the above approach is preferred when you are developing a framework sort of things. For example you develop a web framework and the header takes 5 parameters and these 5 parameters needs to be passed via mark up. Most important thing is that the framework/header is independent
In your controller, you need to require the directive modules. Then assign them to a scope variable which would be that array you have. Will update with code when I get to desktop, tried doing with phone kinda tuff.
I'm trying to understand directive's scope inside ng-repeat, still wondering if it comes from ng-repeat but it looks like.
Here is my code
directive.js
myApp.directive('dirSample', function () {
return {
template: '<input type="text" ng-model="name" />',
replace: true,
restrict: 'AE'
}
});
mainController.js
angular.controller('mainController',function($scope){
$scope.name = 'name'
});
index.htm
<div ng-repeat="i in [1, 2, 3, 4]">
<dir-sample></dir-sample>
</div>
<dir-sample></dir-sample>
<dir-sample></dir-sample>
When i make a change in one of the last two directives (which are not inside ng-repeat) it works well, changes on one are reflected on the other.
Problem :
1 - if i change an input value of a directive generated by ng-repeat , changes are not reflected anywhere else.
2 - if i change value of input on one of the two last directives , the directives inside ng-repeat change too, but if touch ( change input value ) of any directive , changes will not be reflected on that directive but will keep being reflected on the other directives.
Can someone please explain why the scope has that behavior ?
Thanks.
Binding primitives is tricky, as is explained here: Understanding scopes. It has to with how Javascript works. Your 'name' variable will get shadowed once it is altered within the ng-repeat block. The suggested fix (from the link above):
This issue with primitives can be easily avoided by following the
"best practice" of always have a '.' in your ng-models
They also provide a link to a video explaining exactly this problem: AngularJS MTV Meetup
So a fix looks like this:
app.controller('mainController',function($scope){
$scope.attr= {}
$scope.attr.name = 'name'
});
app.directive('dirSample', function () {
return {
template: '<input type="text" ng-model="attr.name" />',
replace: true,
restrict: 'AE'
}
});
I'd like to create a panel directive with a dynamic controller and template so that I can display context-sensitive options in my application. For example, clicking edit on a "Font" setting would show the "Font Options".
I can't find much documentation on it but it looks like it's now possible to specify the controller name on a directive using the name attribute:
app.directive('dynamicPanel', function() {
return {
restrict: 'A',
scope: {
config: '=dynamicPanel'
},
controller: '#',
name: 'ctrlName'
};
});
What I was hoping is that given a panel configuration like this:
vm.panel = {
controller: 'FontCtrl',
template: 'font.template.html'
};
I could then load the panel like so:
<div class="options-panel"
ng-if="vm.panel"
dynamic-panel="vm.panel"
ctrl-name="{{ vm.panel.controller }}"
ng-include="vm.panel.template">
</div>
Unfortunately this does not work. I get the error Badly formed controller string. Also it looks like only the text binding works for the name attribute (#).
Is there a way to bind the name attribute to a dynamic value or an alternative to dynamic controllers in directives.
I figured it out in the end by embracing the component pattern. By registering my options panels as individual directives with their own controllers it simply became a question of rendering the directive markup e.g. <font-options/> and then compiling the "dynamic panel".
http://plnkr.co/edit/Ickkz1GGbDdSbUOUcvfj?p=preview
I have the following directive:
app.directive("mydirect", function () {
return {
restrict: "E",
templateUrl: "mytemplate.html",
}
});
The template from mytemplate.html is:
<input ng-model="name{{comment.ID}}" ng-init="name{{comment.ID}}={{comment.Name}}" />
I load the template several times and for each time I want to change the variable assigned as the ng-model, for example ng-model="name88" (for comment.ID == 88).
But all the loaded templates have the same value.
But when I change comment.ID, all inserted templates become the last ID changed.
First of all, you cannot put expressions, like name{{comment.ID}} in ng-model - it needs to be assigned to a variable.
So, let's change the template to:
<input ng-model="comment.ID" ng-init="comment.ID = comment.Name">
It's not entirely clear what you mean by "load the template". If you mean that you create a mydirect directive for each comment object, then you are probably doing this (or at least, you should be) with something like ng-repeat:
<div ng-repeat = "comment in comments">
<mydirect></mydirect>
</div>
This is convenient - comment is both the variable used in the ng-repeat, and the variable used for the directive's template. But this is not too reusable. What if you wanted to change the structure of the comment object? And what if you wanted to place multiple directive's side-by-side, without the child scope created for each iteration of ng-repeat and assign a different comment object to each?
For this, you should use an isolate scope for the directive. You should read more about it here, but in the nutshell, the way it works is that it allows you specify an internal variable that would be used in the template and bind it to whatever variable assigned to some attribute of the element the directive is declared on.
This is done like so:
app.directive("mydirect", function () {
return {
restrict: "E",
scope: {
// this maps the attribute `src` to `$scope.model` within the directive
model: "=src"
},
templateUrl: '<input ng-model="model.ID">',
}
});
And, let's say that you have:
$scope.comment1 = {ID: "123"};
$scope.comment2 = {ID: "545"};
Then you could use it like so:
<mydirect src="comment1"></mydirect>
<mydirect src="comment2"></mydirect>
Alternatively, if you have an array of comments, whether you create them statically or load from a service call, you could just do this:
<div ng-repeat = "comment in comments">
<mydirect src="comment"></mydirect>
</div>
EDIT: see jsfiddle
I have a list of items, that I show as text via a directive (all is simplified here, it's more comlicated than just text, but it's the same in principle). Like so:
<body ng:controller="BaseController">
...
<div ng:controller="Controller">
<itemdir ng:repeat="item in items" item="item"></item>
</div>
...
<input ng:model="currentItem" />
</body>
When I click it, it should show the content of the clicked item in an input.
items array as well as currentItem belong to BaseController scope.
The directive produces a template (see below) with ng:click which should change BaseController scope's property (called currentItem). However it does not do anything to it (input value is not changed to the new current item). In Batarang for Chrome I can see that the currentItem property is visible and changed in the scope of the directive but not of the BaseController.
module.directive 'itemdir', () ->
restrict: 'E'
replace: true
template: '<div ng:click="show(item)"></div>'
controller: 'EditorController'
scope:
item: '=item'
link: ($scope, $element, $attrs) ->
update = ->
$element.html($scope.item)
$scope.$watch('item', update)
For changing the property I tried a method show(item) which is defined in the BaseController's scope which only assigns the item parameter to $scope.currentItem.
It doesn't work even when I change the ng:click value from show(item) to currentItem = item
I know this is some scope issue, but it seems I still don't grasp all the details of it.
So, looking at the provided jsFiddle we can see that the BaseController is being used both in a directive and in top div. This introduced a subtle issue since it was possible to invoke the show(item) method from top-buttons and HTML produced by directives, but those methods were invoked on different controllers and writing to different scopes.
Now, it is hard to deduce from your question if the use of BaseController in a directive was intentional or not (in the question the directive has the EditorController) but assuming that this was by accident and you want to keep BaseController for a div and still invoke methods on it from a directive you need to take special care when creating isolated scopes (as the name implies those are really isolated so not inheriting from a parent scope). Basically you need to make sure that the show method is available in an isolated scope and points to the right method in the parent scope.
Taking your example you would define your directive like this (please note show : '&ngClick'):
module.directive('itemdir', function () {
return {
restrict:'E',
replace:true,
template:'<div ng:click="show(item)" class="clickable"></div>',
scope : {item : '=', show : '&ngClick'},
link:function ($scope, $element, $attrs) {
$element.html($scope.item)
}
}
});
Here is the working jsFiddle: http://jsfiddle.net/pkozlowski_opensource/M9B93/
In the future you might find AngularJS Batarang extension for Chrome (http://blog.angularjs.org/2012/07/introducing-angularjs-batarang.html) useful as it allows to visualize scopes and their content.