how to create personal scope for each compiled element? - angularjs

When I create new 'wItem' object and want to append compiled DOM to container(not ng-repeat)
container.append($compile(UIService.appendItem())($scope));
var children = container[0].children;
var length = children.length-1;
//take appended element and set new scope Item there
var newEl = children[length];
angular.element(newEl).scope().item = wItem;
UIService.appendItem
function appendWorkItem() {
return '<div layout="column" class=" workItem workItemName" id={{item._id}}child > ' +
'<div>{{item.name}}</div> ' +
' </div>'
}
the result is:
First time
wItem[0] = {_id:1, name: item1}
, it creates wItem and appends to DOM!
second and other times I should be:
wItem[1] = {_id:2, name: item2}
wItem[2] = {_id:3, name: item3}
it creates new wItem and when it appends to DOM it updates previous created elements with the last wItem scope.and I getting this:
I am not so good in it,but I think it passes scope of last element to prev ones. How to fix it?

First, don't manipulate the DOM from your controller. Controllers provide scope and are generally nothing but scoping glue for views and models.
Get familiar with directives because they're practically required. Similarly it is critical to understand scopes. Failing to understand scopes and directives will doom your angular efforts.
Here's a directive implementing your UIService. It can be done other ways.
.directive("workItem", ['UIService', function(uiservice) {
return {
scope: {
item: "="
},
template: function() {
return uiservice.appendWorkItem();
}
}
}])
As has been pointed out you need to familiarize yourself with the ng-repeat directive. An example using the workItem directive and ng-repeat might look like this
<div ng-controller="WorkItems">
<div ng-repeat="item in items">
<work-item item="item"></work-item>
</div>
</div>
Here's a plnkr demonstrating the above directive used with ng-repeat.
I suggest staying away from $compile until you have a good grasp of directives and scopes (and actually have a bona fide reason to use it).

Related

angular directive (2-way-data-binding) - parent is not updated via ng-click

I have a nested directive with an isolated scope. An Array of objects is bound to it via 2 way data binding.
.directive('mapMarkerInput',['mapmarkerService', '$filter', '$timeout',function(mapMarkerService, $filter, $timeout) {
return {
restrict: 'EA',
templateUrl:'templates/mapmarkerInputView.html',
replace: true,
scope: {
mapmarkers: '='
},
link: function($scope, element, attrs) {
//some other code
$scope.addMapmarker = function($event) {
var mapmarker = {};
var offsetLeft = $($event.currentTarget).offset().left,
offsetTop = $($event.currentTarget).offset().top;
mapmarker.y_coord = $event.pageY - offsetTop;
mapmarker.x_coord = $event.pageX - offsetLeft;
mapmarker.map = $scope.currentMap;
$scope.mapmarkers = $scope.mapmarkers.concat(mapmarker);
};
$scope.deleteMapmarker = function(mapmarker) {
var index = $scope.mapmarkers.indexOf(mapmarker);
if(index !== -1) {
$scope.mapmarkers.splice(index,1);
}
};
//some other code
)
}]);
These 2 functions are triggered via ng-click:
<img ng-if="currentMap" ng-click="addMapmarker($event)" ng-src="/xenobladex/attachment/{{currentMap.attachment.id}}" />
<div class="mapmarker-wrapper" ng-repeat="mapmarker in shownMapmarkers" ng-click="setZIndex($event)" style="position: absolute; top: {{mapmarker.y_coord}}px; left: {{mapmarker.x_coord}}px;">
<!-- some other code -->
<div class="form-group">
<label>Name:</label>
<input ng-model="mapmarker.name" value="mapmarker.name" class="form-control" type="text">
</div>
<div class="form-group">
<label>Description:</label>
<input ng-model="mapmarker.description" value="mapmarker.description" class="form-control" type="text">
</div>
<button class="btn btn-danger" ng-click="deleteMapmarker(mapmarker)">Delete</button>
</div>
As you can see I am binding the name and description directly via ng-model and that works just fine. The properties are also available in the parent scope, but neither the delete nor the add works (its changed within the directives scope, but not the parent scope).
As far as I understand these changes should be applied, because I'm calling these functions via ng-click and I have other examples where this works. The only difference is, that I am binding to an array of objects and not a single object / property.
I tried using $timer and updateParent() ($scope.$apply() does not work -> throws an exception that the function is already within the digest cycle) but with no success, so it looks like these changes are not watched at all.
The directive code looks like this:
<map-marker-input ng-if="$parent.formFieldBind" mapmarkers="$parent.formFieldBind"></map-marker-input>
It is nested within a custom form field directive which gets the correct form field template dynamically and has therefore template: '<div ng-include="getTemplate()"></div>' as template, which creates a new child scope - that's why the $parent is needed here.
The binding definitely works in one way, the expected data is available within the directive and if I'm logging the data after changing it via delete or add, it's also correct, but only from the inside of the directive.
Because ng-model works I guess there might be a simple solution to the problem.
UPDATE
I created a plunkr with a simplified version:
http://plnkr.co/85oNM3ECFgCzyrSPahIr
Just click anywhere inside the blue area and new points are added from within the mapmarker directive. Right now I dont really prevent adding points if you delete or edit these - so you'll end up with a lot of points fast ;-)
There is a button to show the data from the parent scope and from the child scope.
If you edit the name or description of the one existing point that will also be changed in the parent scope (bound via ng-model). But all new points or deletions are ignored (bound within the functions called via ng-click).
If you want to update the parent scope, you need to access it via $parent once more,
i change
mapmarkers="$parent.formFieldBind"
to :
mapmarkers="$parent.$parent.formFieldBind"
ng-include create one more scope, so you need to access the parent once more.
http://plnkr.co/edit/27qF6ABUxIum8A3Hrvmt?p=preview

Angularjs assign a ngmodel of element when template is loaded

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>

How to use a dynamically generated value in a template in AngularJS

I have a custom form application written in AngularJS and now I need to use the data from the form in a template. But nothing I've tried seems to work.
I am creating a custom directive like this...
.directive('dynamicModel', ['$compile', function ($compile) {
return {
'link': function(scope, element, attrs) {
scope.$watch(attrs.dynamicModel, function(dynamicModel) {
if (attrs.ngModel == dynamicModel || !dynamicModel) return;
element.attr('ng-model', dynamicModel);
if (dynamicModel == '') {
element.removeAttr('ng-model');
}
// Unbind all previous event handlers, this is
// necessary to remove previously linked models.
element.unbind();
$compile(element)(scope);
});
}
};
}])
This is attached to a form element like this..
<div class="row" ng-repeat="field in customForm.fields">
<label>{{field.displayname}}
<input class="form-control" type="{{field.type}}" name={{field.variable}} dynamic-model="field.databind" placeholder="{{field.variable}}" required="{{field.isRequired}}"></label></div>
This part works great, the field is now 2 way bound to the input form.
However when I later tried to use the same method to show the value in a report computed from the form, I get "field.databind" or at best the resolved databind field name such as "currentUser.name" rather than the value, e.g. "devlux"
I've tried
<div class="row" ng-repeat="field in customForm.fields">
<p>{{field.name}} = {{field.databind}}</p>
Also
<p dynamicModel="field.databind"></p>
</div>
Nothing works unless I put it into an input element, which isn't what I'm trying to do here.
The dynamic model code was pulled off someone elses answer to a question about creating dynamic form elements, and honestly I think it's just a step beyond my comprehension. But assuming that "field.databind" will always be a string literal containing the name of an inscope model, how on earth do I access it in a normal template?
{{field.databind}} will be evaluated against the current $scope and will result in whatever $scope.field.databind is, for example the string currentUser.name.
Angular has no way of knowing that currentUser.name isn't the string you want, but actually another expression that you want to evaluate.
To evaulate it again you will need to add a function to your $scope that uses the $parse service.
For example:
$scope.parseValue = function (value) {
return $parse(value)($scope);
};
In HTML:
<div class="row" ng-repeat="field in customForm.fields">
<p>{{field.displayname}} = {{parseValue(field.databind)}}</p>
</div>
The argument that gets passed to parseDynamicValue will for example be currentUser.name. Then it uses the $parse service to evaulate the expression against the current $scope, which will result in for example devlux.
Demo: http://plnkr.co/edit/iPsGvfqU0FSgQWGwi21W?p=preview

Angular: What is a good way to hide undefined attributes that exist in isolated scopes?

I wanted to rewrite this fiddle as it no longer worked in angular 1.2.1. From this exercise, I learned that a template is apparently always needed now in the isolated scopes.
somewhere in the directive:
template: '<p>myAttr1 = {{myAttr1}} // Passed by my-attr1<br>
myAttr2 = {{myAttr2}} // Passed by my-alias-attr2 <br>
myAttr3 = {{myAttr3}} // From controller
</p>',
I was not able,however, to successfully add this to the template:
<p ng-show="myAttr4">myAttr4= {{myAttr4}} // Hidden and missing from attrs</p>
What is a good way to hide undefined attributes that are defined on the isolated scope but not given a value from the dom?
my humble fiddle
EDIT: I use a directive called my-d1 to encapsulate the bootstrap tags. I use my-d2 to demo how to use the # in isolated scopes.
Working version merged with Sly's suggestions
I ran into the same template issue in Angular 1.2.0, see the first entry in the 1.2.0 breaking changes:
Child elements that are defined either in the application template or in some other directives template do not get the isolate scope. In theory, nobody should rely on this behavior, as it is very rare - in most cases the isolate directive has a template.
I'm not exactly sure what the issue is that you are encountering - it might be some incorrect markup or you are misnaming the scope variables listed in your isolate scope.
Using ng-show will correctly hide the element if the attribute has not been passed in.
i.e. your example here is correct: <p ng-show="myAttr4">myAttr4= {{myAttr4}}</p>
Updated version of your Fiddle: http://jsfiddle.net/Sly_cardinal/6paHM/1/
HTML:
<div ng-app='app'>
<div class="dir" my-directive my-attr1="value one" my-attr3='value three'>
</div>
<div class="dir" my-directive my-attr1="value one" my-attr3='value three' my-attr4='value four'>
</div>
</div>
JavaScript:
var app = angular.module('app', []);
app.directive('myDirective', function () {
return {
// can copy from $attrs into scope
scope: {
one: '#myAttr1',
two: '#myAttr2',
three: '#myAttr3'
},
controller: function ($scope, $element, $attrs) {
// can copy from $attrs to controller
$scope.four = $attrs.myAttr4 || 'Fourth value is missing';
},
template: '<p>myAttr1 = {{one}} // Passed by my-attr1</p> '+
'<p ng-show="two">myAttr2 = {{two}} // Passed by my-alias-attr2 </p>'+
'<p>myAttr3 = {{three}} // From controller</p>'+
'<p ng-show="four">myAttr4= {{four}} // Has a value and is shown</p>'
}
});

How do I inject a variable into a directive's scope?

Let's say I've got a directive that looks like this:
directive('attachment', function() {
return {
restrict: 'E',
controller: 'AttachmentCtrl'
}
})
That means I can write a list of 'attachment' elements like this:
<attachment ng-repeat="a in note.getAttachments()">
<p>Attachment ID: {{a.id}}</p>
</attachment>
In the above snippet, let's assume that note.getAttachments() returns a set of simple javascript object hashes.
Since I set a controller for the directive, I can include calls to that controller's scope functions inline.
Here's the controller:
function AttachmentCtrl($scope) {
$scope.getFilename = function() {
return 'image.jpg';
}
}
And here is the modified HTML for when we include a call to that $scope.getFilename function inline (the new 2nd paragraph):
<attachment ng-repeat="a in note.getAttachments()">
<p>Attachment ID: {{a.id}}</p>
<p>Attachment file name: {{getFilename()}}
</attachment>
However, this isn't useful. This will just print the same string, "image.jpg", as the file name for each attachment.
In actuality, the file name for the attachments is based on attachment ID. So an attachment with ID of "2" would have the file name of "image-2.jpg".
So our getFilename function needs to be modified. Let's fix it:
function AttachmentCtrl($scope) {
$scope.getFilename = function() {
return 'image-' + a.id + '.jpg';
}
}
But wait — this won't work. There is no variable a in the scope. We can use the variable a inline thanks to the ng-repeat, but that a variable isn't available to the scope bound to the directive.
So the question is, how do I make that a available to the scope?
Please note: I realize that in this particular example, I could just print image-{{a.id}}.jpg inline. But that does not answer the question. This is just an extremely simplified example. In reality, the getFilename function would be something too complex to print inline.
Edit: Yes, getFilename can accept an argument, and that would work. However, that also does not answer the question. I still want to know, without workarounds, whether you can get a into the scope without using it inline.
For example, maybe there is a way to inject it directly into the controller so it would be written as:
function AttachmentCtrl($scope, a) { ... }
But where would I pass it in from? Is there something I can add to the directive declaration? Maybe an ng-* attribute I can add next to the ng-repeat? I just want to know if it's possible.
But wait — this won't work. There is no variable "a" in the scope. We can use the variable a inline thanks to the
ng-repeat, but that a variable isn't available to the scope bound to
the directive.
Actually variable a is in the scope associated with the directive controller. Each controller created by the directive gets the child scope created by the ng-repeat iteration. So this works (note $scope.a.id):
function AttachmentCtrl($scope) {
$scope.getFilename = function() {
return 'image-' + $scope.a.id + '.jpg';
}
Here's a fiddle that shows the controller scope, directive scopes, and ngRepeat scopes.
"If multiple directives on the same element request new scope, only one new scope is created. " -- Directive docs, section "Directive Definition Object"
In your example, ng-repeat is creating a new scope, so all directives on that same element get that same new (child) scope.
Also, if you do ever come across a case where you need to get a variable into a controller, using attributes would be better than using ng-init.
Another way would be to use ng-init and set a model property for child scope. See this fiddle
Relevant code would be
<div ng-app='myApp' ng-controller='MyCtrl'>
<attachment ng-repeat="a in attachments" ng-init='model=a'>
<p>Attachment ID: {{model.id}}</p>
<p>Attachment file name: {{getFilename()}}</p>
</attachment>
</div>
and
function AttachmentCtrl($scope) {
$scope.getFilename = function () {
return 'image-' + $scope.model.id + '.jpg';
}
}
Just pass it into your function.
View:
<attachment ng-repeat="a in note.getAttachments()">
<p>Attachment ID: {{ a.id }}</p>
<p>Attachment file name: {{ getFilename(a) }}
</attachment>
Controller:
function AttachmentCtrl ($scope) {
$scope.getFilename = function (a) {
return 'image-' + a.id + '.jpg';
}
}

Resources