Angularjs Binding 2 directives scope models to a parent controllers models - javascript

I have a form controller with 2 object models model1:{name:"foo"} model2:{name:"model2"}
I created 2 directives (both which create isolated scopes) . One with Element only binding which uses model1 and another one with Attribute only binding which uses model2.
The nesting is like so:
<div myattibute="model2">
<mytag my-model="model"></mytag>
</div>
The attribute only directive doesn't have a template and the tag directive has a template.
The problem is that I am getting undefined in mytag directive for the model.
1.Can someone see the problem and explain it in the plnkr ?
http://plnkr.co/edit/Q23XqY?p=preview
Partial Solution:
A working example with adding an empty div template with just ng-transclude for the myattribute directive makes it work. With i mandated that this attribute directive is on a div it that I would have wanted it to be placeable on any div, span etc.
Here is the working example:
http://plnkr.co/edit/z0M5ys?p=preview
2.How is ng-transclude affecting scope inheritance?
3.Can't I create this attribute with only business logic without any markup ?

Isolate scopes are best avoided except for rare cases they add unnecessary complexity. It is much simpler to just use $scope.$watch to bind to expressions in attributes like this:
$scope.$watch(attrs.myModel, function(newValue, oldValue) {})
$scope.$watch(attrs.myattribute, function(newValue, oldValue) {})
This way your directives can either share the parent scope they were declared in and handle binding to it using $watch expressions, or they can create a child scope using { scope: true } if needed.
Here is one possible solution: http://plnkr.co/edit/mm2q67?p=preview
Keep in mind that your myTag directive can use an isolate scope if you'd really like to do it that way, but the myattribute one can't as that will break the scope inheritance chain for myTag

Related

AngularJS: Dynamically add/remove wrapping directives to existing ones

So I need to be able to call a function of a directives controller, have it wrap an existing child directive in a new one while maintaining the now wrapped directive's state (both it's controller and all possible children) and otherwise modify the parent directive's template. This process also has to able to reversed.
Quick and super dirty fiddle to show the idea
So the DOM starts as:
<split>
<innerDirective></innerDirective>
</split>
When the split directive's controller's function is called, this should happen:
<split> // same outer parent
<split> // new directive
<innerDirective></innerDirective>// SAME innerDirective instance
</split>
<split>// new directive
<innerDirective></innerDirective> //New innderDirective instance
</split>
</split>
I originally used the .html function to set the split directives content, and used $compile service to reconstruct the DOM. This of course destroys the original innerDirective. Since it may be deep, it is not feasible to store it's data in the parent split and retrieve with a link function (although I tried).
I also tried using jquery's .wrap and .unwrap functions, which works, except the added tags aren't compiled as directives, so not really.
Is there a proper way to do this, or a way to compile just the outer tag added with the .wrap, and update the scope of the innerDirective to the new parent?
Thanks for any help and advice you may have!
Edit:
I have a controller along this lies, lets say set to control the split 'E' directive:
function SplitCtrl($scope, $compile){
//stuff
//First way, redefine html contents and recompile
this.split1 = function(){
//element has been linked to the directive's element
$scope.element.html('<split></split><split></split>');
$compile($scope.element.contents())($scope);
};
//Second way, jquery.wrap
this.split2 = function(){
$($scope.element).children('innerDirective').wrap('<split />');
// then I need to instantiate the new split
// without reinstantiating now wrapped directive.
// Adding second split is fine in this case, I can do that.
};
};
The template is:
<innderDirective></innerDirective>

AngularJS : Isolated Scope in directive with Compile in directive

I am struggling to get access to an isolated scope from some html I am building in the compile function of a directive. I have a SIMPLE example, the one I am working with is much more complex. Essentially I can't access properties on the isolated scope. Here is the code.
http://plnkr.co/edit/ETIRs4j3EwZ4DFN5XkPc?p=preview
It's odd to me because the in the console the scope looks great, from the post function.
Notice how the html is set up in the compile function:
tElement.append(angular.element('<div>{{name}}</div><span>{{value}}</span><span>Hello</span>'))
Also notice the second directive, Here with an inherited scope everything works fine.
Edit: I updated the first directive(The directive that isn't working) to set a property in the html so I could look for it in the console when I log the scope. It turns out it is on the $parent scope (The property is named "wat"). But why? It makes sense why it wouldn't work, but I don't understand why that html doesn't have access to the same isolated scope? Is there a way to force the html I am inserting to use the isolated scope instead?
Edit 2: Ok so a lot of questions about why I am trying to do this, the best description could be found here. https://github.com/angular/angular.js/issues/7874. Essentially we wanted to ng-repeat some transcluded content, and expose the item from ng-repeat to the transcluded content, but you can't do that in angular, because the transcluded content only has access to the parent scope.
The goal is to get the first directive to work like the second, in that the value of the two properties (value, and name) work from appending html in the compile function, with an isolated scope.
This is how I would go about what ( I think ) you are trying to achieve
http://plnkr.co/edit/mL7h7Hf8TkkpfsPNoL6g?p=preview
By using a template with transclude: true and a link function you can achieve the desired result as you replace the element with your template while interpreting the angular expressions
You could use replace: true to further customise your directive
app.directive('widget',function(){
return {
restrict:'E',
transclude: true,
template: '<span ng-bind="wat=\'YES\'"></span><div>{{name}}</div><span>{{value}}</span> <span>There</span>',
scope:{
name:'#',
value:'='
},
link:function($scope, tElement){
}
}
});

Angular Isolate Scope and Attributes

In the following plunk:
http://plnkr.co/edit/ss3HTb?p=info
I am attempting to bind an attribute to an isolate scope. However, it is not working. I'm wondering if anyone knows why that is?
I've looked through several resources, including:
Need some examples of binding attributes in custom AngularJS tags and http://www.sitepoint.com/practical-guide-angularjs-directives-part-two/
You specified the isolate scope as
scope: {
attr: "=attributeName"
}
What that does is take the value of attributeName specified in your html as an expression in your scope and bind that to attr in your directive scope. But what you did was just to specify "hello" without having a hello object in your scope. To get it to work the way you have it now, you have to use the # attribute binding like so:
scope: {
attr: "#attributeName"
}
The other change that you need to do is how you specify the value in the html. You use titleCase when you define the attribute, but in the html instead of titleCase you need to seperate the "words" by a dash and use all lowercase. So attributeName becomes attribute-name
<node attribute-name="hello"></node>
On the other hand, if you actually want to bind to the value of an object in your scope, then make sure that you specify it in your scope and then you can use it in your directive (and preferably use the dot notation).
I have you updated your plunker to show you how they work.

Angular multiple instances of same directive, scope is not isolated

I have a problem when creating multiple directives with isolated scope: when I change something in 1st directive it also makes changes in all other directives.
Here is a working example: http://plnkr.co/edit/drBghqHHx2qz20fT91mi?p=preview
(try to add more of Type1 'Available notifications' - a change in 1st will reflect in all other directives of Type1)
I found some solutions to similar problems here but they don't work in my case. Also found a working solution with mapping 'subscription' data to local scope variables in directive (app.js, line 76) but I think there should be a more general way to do this right?
In your directive 'notificationitem' you have the following code, keep it in mind as i explan:
// if all variables are mapped in this way than works
//$scope.enabled = $scope.subscription.enabled;
The reason why all of the 'isolated' scopes are updating is because of this code in your scope declaration in the same directive (notificationitem):
scope: {
subscription: '=',
index: '#'
},
The equal sign on subscription is angular's way of saying "Whenever the current scope updates, go to the parent and update that value as well." This means whenever you update your 'isolated' scope, it updates the parent scope as well. Since all of these isolated scopes are binding to the parent, they will change as well.
Since you want the subscription.value to be the default value of that text field, you will need to do exactly what your commented code is doing:
scope.value = scope.subscription.value;
This will create an isolated value inside of the isolated scope. When scope.value changes, scope.subscription.value will not. All of the text fields now have their own 'value' to keep track of.
Check out this article for information on directive bindings: http://www.ng-newsletter.com/posts/directives.html
Also, another way to get the default value would be to inject your service into the directive, if you don't like the above solution. Hope this all helps.

In AngularJS, how to make an isolated scope inherit from ng-repeat's scope

I'm trying to create a custom component that receives arguments in a ng-repeat loop.
So for example, say I have a component named "mycomp" that receives a custom argument "name" in a ng-repeat:
<mycomp name="{obj.name}" ng-repeat="obj in list" />
And in my directive the isolated scope is defined like this:
scope:{name:"#"}
That won't work because ng-repeat creates an isolated scope for each element it iterates. So I ended up having two levels of scopes.
How do I get around this issue? Am I doing something wrong?
Thanks.
As I stated in my comment of your original question, this has already been answered. Anyway, here it is, summed up:
In your template, state the model you want to have inherited, without {{}} (as using brackets results in the value being passed, and not the reference to the model itself):
<mycomp name="obj.name" ng-repeat="obj in list" />
And in your directive, establish a 2-way binding, like so:
scope:{name:"="}
EDIT:
I realize now (after your comment) that while this solves your problem, it doesn't fully answer the question. Here goes:
When you create a directive you have the choice of creating a scope that inherits from its parent (controller, typically, though not necessarily) ou an "isolated" scope, by specifying scope: true or scope: {...}, respectively.
So, by creating an unisolated scope, all the parent's models are available (you can access scope.obj - created via ng-repeat - but also scope.list). This is convenient, but also dangerous, of course (and doesn't really create reusable code).
If you create an isolated scope, you can specify the scope's models using '#', '=' or '&'.
'#' and '&' both produce a isolated, unbinded value, (that, if you change, changes only on the isolated scope - in your case, the object in the original list suffers no change at all), the only difference being that '#' reads a string value, and '&' reads an expression.
THIS IS IMPORTANT: the reason why I believe your code didn't work was (only) because you passed name="{obj.name}" and not name="{{obj.name}}", for with '#' the string value is read, and that string value can be the name of obj, but you must include it in {{}}!
If you use '=', you are declaring that you want that variable to be binded with the specified outside variable. So, if (in a fit of crazy, crazy rage!) you want to have 2 models in your directive that start up with the same value, but on is binded (i.e. changes are propagated to the outside scope), you could do something like this:
<mycomp binded-name="obj.name" unbinded-name="{{obj.name}}" ng-repeat="obj in list" />
and in your directive:
scope:{
bindedName: "=",
unbindedName: "#"
}

Categories