Using an updated array of objects inside a directive - javascript

Within a view I'm using ng-repeat inside a directive to loop over an array of objects from my controller. During this phase the objects inside the array suffer value changes. When I change the view and create a new instance of the same directive, I want to loop over the array of objects yet again but this time loop over the updated array of objects.
If I modify the objects in the first view and pass the same array of objects in the second view, the directive uses the initial array, not the updated one. What I tried is watching the array for changes, it sees the changes but still during the second instance of the directive it uses the initial array.
Can someone explain whats going on and how could I solve this ?

You can have a simple parent child relationship directive, in which directive have access to scope of controller o the view in which it's added. so you can have ng-repeat in directive & array in scope variable of controller. Then automatically the values will be sync by two way data binding. You can follow following plunk I've created.
It depends upon how are you sharing data between controller & directive. Two data binding helps make it sync else you've to handle it over event or using $watch in directive (if you're using jquery code inside directive).
https://plnkr.co/edit/m47TPgcX7lY8nL1LswCv?p=preview
app.directive('helloWorld', function ($compile, CarCompany) {
return {
restrict: 'E',
template: '<div>\
<h2>HelloWorld directive</h2>\
<p ng-repeat="car in carc">{{car.name}}</p>\
<h3>Liste des marques:</h3>\
</div>',
link: function (scope, elem, attrs) {
var ul = document.createElement('ul');
var lis = '';
angular.forEach(CarCompany, function(item) {
lis += '<li>' + item.name + '</li>';
});
ul.innerHTML = lis;
elem.append(ul);
$compile(ul)(scope);
},
};
});
This's simple directive showing shared scope between controller & directive.
https://www.infragistics.com/community/blogs/dhananjay_kumar/archive/2015/06/11/understanding-scopes-in-angularjs-custom-directives.aspx

Related

AngularJS ng-init not initializing variable globally

I am using ng-repeat of angularjs directive to load the array which have JSON value store in it. and value has subarray also.
<div ng-repeat="data in MENULIST" > //MENULIST have array(Data)
after checking some conditions like (if:condition).
<div ng-if="(data.SubMenu.length >0)" ng-init = "MENULIST = data.SubMenu"></div>
but that assignment or initialization is not been done globally to MENULIST it limited to this div Only.
ng-repeat stop after printing two main array elements.
not printing Sub Element of Array Element.
actually, I am trying to make and tree structure in the sidebar that has menu and sub menus also.
Besides two-way binding should work for you I believe calling some function is more straightforward way(especially since you can create this function in scope of particular controller - so it would be much easier to get the point)
<div ng-if="(data.SubMenu.length >0)"
ng-init = "someController.setMenuList(data.SubMenu)">
</div>
Another "angular-way" is adding explicit watcher to data.Submenu.length in component's JS code
$scope.$watch('data.Submenu.length', function (value, oldValue) {
if (!value && oldValue) {
// do your init
}
});

AngularJS formatter in nested directive

I have a really weird Problem about formatters in nested directives.
I want a modelType directive for formatting.
If I don't check the "modelType", it works.
All View-Values are changed to "Formatted: ...".
But if i implement the if (attrs.modelType == "testType") { ... } it wont work, but i don't know why.
myApp.directive('modelType', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
if(attrs.modelType == "testType") {
ngModel.$formatters.push(function(value){
//formats the value for display when ng-model is changed
return 'Formatted: ' + value;
});
ngModel.$parsers.push(function(value){
//formats the value for ng-model when input value is changed
return value.slice(11);
});
}
}
};
Does anyone know this Problem?
http://jsfiddle.net/nkop2uq0/2/
First, using the attribute model-type both in the outer and inner directive led to the inner's link function getting executed twice (once for the controller-owned HTML, once for the template HTML), overwriting your formatting. So you need (or at least I think you should) disentagle the attribute for the two directives by using a different attribute for the inner directive:
template: '<div><input type="text" ng-model="ngModel" my-type="modelType" /></div>'
and consequently rename the inner directive to
myApp.directive('myType', function(){
Next, since the inner directive now does no longer get called by the compile step for the outer one, you need to compile the template for the inner directive in your post-link function
This can be done like this:
link: function (scope, element){
$compile(element.innerHtml)(scope)
}
This leads to attrs being always the same, so you can't use it to test for your testType:
$$element: U[1]
$$observers: Object
$attr: Object
myType: "modelType" <-- see here
ngModel: "ngModel"
type: "text"
Therefore you need to to look for the actual value inside scope:
if(scope.modelType == "testType") {
Lastly (and frankly I would be happy if someone could explain this to me, I don't understand it), I had to define the modelType scope property in the outer directive as one-way bound via
modelType: '#modelType'
I put this together in an updated fiddle here: http://jsfiddle.net/nkop2uq0/8/
NB: I am far from understanding the intricacies of Angular's directives. As much as I like the framework, the number of ways things can be achieved is mind-boggling. So you should try to improve my answer, it is most likely not best practice. And drop me a line if you find bad ideas in there...

Sharing temp variable between two same custom angular directive

I have created custom directive and applied it two times on the same html page.
In the controller of the directive I have function that sets value of some TEMP variable. When I use let's say first directive and inside of it set value of this TEMP variable and after that when I use the second directive value of TEMP variable is not available.
How can I achieve this ?
I would recommend having a service or controller (depending on whether a controller is applicable in this case) that sits above the directives. It can hold the base copy of the variable, and be read from or written to.
For example, in your directive:
localTemp = tempService.temp;
// or scope.temp if pulling scope from a controller like link: function (scope, elem, attr) {...}
...
tempService.temp = localTemp;
You may have to put this second line inside a $timeout(...) if you have this variable directly visible on your page in order for the change to be picked up by the Angular digest, propagated and displayed in your UI.

Angular controllers: Accessing correct variable in controller using custom attribute with variable name

I created a custom <select> dropdown element using a directive, which has a selecting attribute that is used in the directive to pull the correct data from the controller to populate the list of options. I'd like to know if this is an appropriate use of AngularJS's different pieces. I'm new to Angular, but this is how it occurred to me to accomplish this task.
In the HTML partial for a page needing my version of select, this is all that needs to be written:
<sliding-select selecting="areas"></sliding-select>
In the controller there is a corresponding variable on the main object called areas which contains within it an array called options, whose members should be the options of the area select.
And here is the part of the directive that takes that data and creates an option element for each member of the areas array :
.directive('slidingSelect', function($timeout) {
return {
restrict: 'E',
compile: function(element, attrs) {
var selecting = attrs.selecting;
// ng-repeated options come from selecting attr
var dropDownList = $('<div class="sliding-select-list"></div>');
dropDownList.append('<div class="sliding-select-option" ng-repeat="item'+
' in home.'+selecting+'.options">{{item.name}}</div>');
element.append(dropDownList);
}
};
I'm just a bit worried that it seems hacky to create an element whose ng-repeat phrase uses the variable name by creating a string from it. Maybe there's a totally different approach to this problem that's preferred by the Angular community?

How do I pass many values to an Angularjs directive?

I'm creating a reusable bit of html via a directive. The html would have a few variables that I want passed from the original scope. This is done easily by declaring attributes on the directive, then creating an isolate scope and capturing them. The question is, is there a better way to do this for a larger number of variables? I had thought of passing in an object like {firstAttr: $scope.one, secondAttr: $scope.two...} and picking this object apart to get each piece. This works the first time, but the two-way databinding doesn't work (even using the '=').
The problem is the thing that is bound is the object, not each of the individual parts of the object. Could I maybe use the compile function in the directive to add each of the attributes to the element or something? so:
<mydirective databinding="{one:'first one', two:'second one'}">
would be translated into:
<mydirective one="first one" two="second one">
That way my databinding would work as expected by capturing the attributes in the directive. How would I go about accomplishing that design, or is there just another way completely to do this?
The databinding directive idea is an interesting one but it's not the way I would do it since I believe you'd run into directive priority issues, plus the fact that it's very non-standard and would make your code hard to follow for future programmers. There's several ways to do this so I'll discuss 3 different solutions I've used.
Solution 1
If you only need one way data binding, the simplest solution is to use angular's scope.$eval function on the string representation of the object inside your directive after interpolating any simple scope variables on it using {{}}. The string representation doesn't even have to be valid JSON, since you'll notice in the example below I don't include quotes around the object keys.
In the view:
<div databinding="{one:'first', two:{{scopeVar}}, complex:[1,2, "Hi"]}"></div>
And in the javascript:
app.directive('databinding', function () {
return{
link: function (scope, elm, attrs) {
console.debug(scope.$eval(attrs['databinding']));
}
}
});
Solution 2
Another one-way data binding solution is to create an option object inside the controller and pass it to the directive using "#" (or even "="):
In the controller:
$scope.options = {one: "first, two: "second"};
In the view:
<div databinding="options"></div>
And in the javascript:
app.directive('databinding', function () {
return{
scope: {
options: "#" //Can also use = here
},
link: function (scope, elm, attrs) {
console.log(scope.options);
}
}
});
Solution 3
If you do need two way data binding, you're mostly out of luck, as there's no elegant way to do it. HOWEVER, if you're in the market for hackish solutions, you can accomplish two way data binding with a method very similar to solution 2 but with a change to the option object.
Instead of declaring an option object containing simple primitive data types like strings, create a dummy object inside the option object, which you then declare your variables inside of. Doing it this way, changes to scope variables in your controller will also be realized inside the directive, as demonstrated through the timeouts.
Controller:
$scope.someScopeVar = "Declared in controller"
$scope.options = {
dummy: {
one: $scope.someScopeVar,
two: "second"
}
}
window.setTimeout(function(){
$scope.someScopeVar = "Changed in controller";
}, 2000)
View:
<div databinding="options"></div>
Directive:
app.directive('databinding', function () {
return{
scope: {
options: "=" //You need to use = with this solution
},
link: function (scope, elm, attrs) {
console.log(scope.options.dummy.one); //Outputs "Declared in controller"
window.setTimeout(function(){
console.log(scope.options.dummy.one) //Outputs "Changed in controller"
}, 5000)
}
}
});
This method works since javascript passes objects by reference whereas primitives are copied. By nesting an object in an object the data binding is preserved.
You can change scope in directive as follows
.('mydirective ', function(){
var linker = function(scope, element){
console.log(scope.one, scope.two);
}
return {
link: linker,
scope: {one:"=", two:"="}
}
});
The question is, is there a better way to do this for a larger number of variables?
I don't think so. As you already found out, trying to pass them as one object results in the object being databound, not the individual parts.
Even if you could get something working with $compile, it would not be obvious to other people reading your code what is going on.
Another option is to either not create a scope (scope: false, which is the default for directives), or create a new child scope (scope: true), but require that the calling scope must use certain scope property names to use the directive. Then you don't have to specify any attributes. This makes the directive's use much more restrictive, but I think those are your two choice: specify multiple attributes, or require certain scope property names.

Categories