Angular model looses scope with ng-included in directive [duplicate] - javascript

This question already has answers here:
Losing scope when using ng-include
(4 answers)
Closed 8 years ago.
I wasn't sure on how to properly title this question, so here it goes ..
I'm trying to setup a dynamic way to change out templates inside of a directive using the ng-include method. I've set up two Plunker examples and although one should work just like the other, that doesn't seem to be the case.
HTML for both Examples:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<main></main>
</body>
</html>
Example #1:
http://plnkr.co/edit/bi3Plrm8xufuN79Nvajj?p=preview
I'm setting up two directives (one main, and one nested as a child):
angular.module('myApp', ['Test']);
angular.module('Test', [])
.directive('main', [
function () {
return {
restrict: 'E',
template: '<input type="text" ng-model="myModel"><br><br><child></child>'
};
}
])
.directive('child', [
function () {
return {
restrict: 'E',
template: '<input type="text" ng-model="myModel">'
};
}
]);
Easy. When running the app both fields populate respectively as the model changes.
Example #2:
http://plnkr.co/edit/3ajcTyfJElEzbqvsWwBM?p=preview
The HTML stays the same, but the js is a little different:
angular.module('myApp', ['Test']);
angular.module('Test', [])
.directive('main', [
function () {
return {
restrict: 'E',
template: '<input type="text" ng-model="myModel"><br><br><child></child>'
};
}
])
.directive('child', [
function () {
return {
restrict: 'E',
controller: function($scope) {
$scope.myTemplate = 'test-template.html'
},
template: "<div ng-include='myTemplate'></div>"
};
}
]);
test-template.html:
<input type="text" ng-model="myModel">
This time, if I interact with the first input that is generated, both inputs update respectively as they should. Here's when it gets interesting ... When/if I interact with the second input (the one generated by ng-include) I loose all binding. Forever... Almost as if it's created its own version of the model. Afterwards, changing the first input has no effect on the second.
What is happening here? Is it indeed creating a new instance of myModel? And if so, how can this be avoided when using this ng-include method?

This is no weird, as PSL said,
ng-include creates new scope.
If you want to create the behaviour that keeps those models attached,
You should change
<input type="text" ng-model="myModel">
To:
<input type="text" ng-model="$parent.myModel">

Related

Get Height for multiple elements without JQuery in AngularJS

Fairly new to Angular and still trying to wrap my head around a few things.
I need to get the height of multiple items on a dashboard. I have seen this answer:
Get HTML Element Height without JQuery in AngularJS
However, I can't work out how to get it to work for multiple items. Surely I don't need to write a separate directive for each element.
So playing with this Plunker, I changed the html to below, but get identical values for both elements.
hmm
script.js:
angular.module('myModule', [])
.directive('myDirective', function($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
scope.height = element.prop('offsetHeight');
scope.width = element.prop('offsetWidth');
}
};
})
;
and the html:
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#*" data-semver="1.2.13" src="http://code.angularjs.org/1.2.13/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="myModule">
<h1 my-directive>Hello Plunker! width: {{width}}, height: {{height}}
</h1>
<h3 my-directive>Smaller Hello Plunker! width: {{width}}, height: {{height}}
</h3>
</body>
</html>
The directives have no scope, so they are storing the values on $rootScope. The values reflect the height and width of the last directive to execute. Fix your directive to use either inherited scope or isolate scope.
angular.module('myModule', [])
.directive('myDirective', function($timeout) {
return {
restrict: 'A',
//set scope to inherited
scope: true,
//OR use isolate scope
//scope: {},
link: function(scope, element) {
scope.height = element.prop('offsetHeight');
scope.width = element.prop('offsetWidth');
}
};
})
;
By setting the scope property, the $compile service will create a new scope (either inherited or isolate) for the directive to store values unique to the directive.
For more information on directive scopes, see AngularJS Comprehensive Directive API -- scope.
I found this script to be very helpful
https://github.com/pocesar/angular-track-height

Angular nested directive only the first child is rendered [duplicate]

This question already has answers here:
AngularJS - html under my directive is not showing
(2 answers)
Closed 6 years ago.
I have a parent directive which includes two children directives, first and second. I noticed that only the first child is rendered. Also, if I put some arbitrary HTML markup before the first one, it's all rendered but if I put them after that then they will not show up. Why is this?
See the jsfiddle:
<!-- index.html -->
<div ng-app="myApp">
<my-parent-dir />
</div>
<!-- main.js -->
var app = angular.module("myApp", []);
app.directive("myParentDir", function() {
return {
restrict: 'E',
template: '<my-first-child /> <my-second-child />'
};
});
app.directive("myFirstChild", function() {
return {
restrict: 'E',
template: '<input type="text" placeholder="first">',
};
});
app.directive("mySecondChild", function() {
return {
restrict: 'E',
template: '<input type="text" placeholder="second">',
};
});
Try to use it like this:
var app = angular.module("myApp", []);
app.directive("myParentDir", function() {
return {
restrict: 'E',
template: '<my-first-child></my-first-child> <my-second-child></my-second-child>'
};
});
From the angular issues in github:
self-closing or void elements as the html spec defines them are very
special to the browser parser. you can't make your own, so for your
custom elements you have to stick to non-void elements ().
this can't be changed in angular.
Self defined tags are no leaf tags so you will have to use:
template:'<my-first-child></my-first-child> <my-second-child></my-second-child>'
Since you're using custom tags, you need to close the tag, because HTML spec does not allow self closing tags.
template: '<my-first-child></my-first-child> <my-second-child></my-second-child>'
JSFiddle

angularjs transclude and ng-repeat: doing it right

I have the following code that produces not the expected result:
<div class="outer">1<div>content</div></div>
<div class="outer">2<div>content</div></div>
<div class="outer">3<div>content</div></div>
<div class="outer">4<div>content</div></div>
Instead the result is:
<div class="outer">1</div>
<div class="outer">2</div>
<div class="outer">3</div>
<div class="outer">4<div>content</div></div>
It seems that the ng-repeat gets done first and for the last item it handles the transclude. I know that ng-repeat create the nodes during the compile phase, but I thought in the link phase the link function is called for each node and adds the embedded content.
Can somebody explain what is happening here and how to do it correct?
<!DOCTYPE html>
<html ng-app="Transclude">
<head lang="de">
<meta charset="UTF-8">
<title>Transclude</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
</head>
<body>
<outer ng-repeat="counter in [1,2,3,4]" value="counter">
<div>content</div>
</outer>
<script>
angular.module('Transclude', [])
.directive('outer', function () {
return {
restrict: 'E',
scope: {
value: '='
},
replace: true,
transclude: true,
template: '<div class="outer">{{value}}</div>',
link: function(scope, element, attributes, controller, transclude) {
var transcludedContent = transclude();
element.append( transcludedContent );
}
};
})
</script>
</body>
</html>
transclude() by itself just links the content of the directive to the proper scope and returns it. What you want to do is actually clone the content (make copy of it), before transclude links it. As you code stands right now, your transcluded content is just getting moved from one instance to another - ending up on the last one because, well, it's the last one.
You can do this with the cloneAttachFn. You pass it in to transclude.
link: function(scope, element, attributes, controller, transclude) {
transclude(scope, function(clone) {
element.append( clone );
});
}

AngularJS - calling methods on the parent scope from isolated scope directive not passing arguments

I'm just learning angular and creating some simple directives to try some things. I am having (what I think) is a small problem attempting to pass parameters from the directive to a controller function on the root scope.
Please see the following jsfiddle and note that I clicking the button (from within the directive) gives me undefined whereas it seems to work fine if clicking the button from the controller itself.
jsfiddle
Am I just missing something syntax wise? Or am I completely wrong in how this should work? I have made several attempts at placing variables in different locations (note the 'xxx') in the fiddle to see if anything would work and I get either errors or nothing.
<div ng-app="myApp" ng-controller="myController">
<!-- root scope -->
<div style="background-color: teal">
<button ng-click="propertyF('yyy')" >F</button>
</div>
<!-- directive firing methods on the root scope -->
<div style="background-color: coral">
<my-directive3 property6="propertyF()"></my-directive3>
</div>
</div>
var app = angular
.module('myApp', [])
.controller('myController', [
'$scope', function($scope) {
$scope.propertyF = function (aValue) {
alert("propertyF fired: '" + aValue + "'");
};
}
])
.directive('myDirective3', function() {
var directive = {
link : function link(scope, element, attrs) {
console.log("link directive 3");
},
restrict : 'EA',
replace : true,
scope : {
property6: '&'
},
template: '<button ng-click="property6(\'xxx\')">property6</button>'
};
return directive;
});
With the way Angular works, are you passing a function binding and specifying arguments. propertyF() does not specify any arguments.
property6="propertyF(arg)"
Then you can do Angular's unique syntax for handling this:
ng-click="property6({arg:\'xxx\'})"
http://jsfiddle.net/ue1trkt9/1/

Better granularity in Angular directive restriction

Is there a way to write a directive that just applies to a specific element + attribute + attribute value?
My very first intention would be to have separate directives, for modularity and maintenance purposes, but I'm afraid that's not possible as I get an error from Angular telling me there are multiple directives matching the element.
So my scenario is as follows: I want to write my own input elements, e.g.
<input type="time-picker">
<input type="date-picker">
so I did
app.directive('input', function () {
return {
restrict: 'E',
templateUrl: function ($element, $attrs) {
if ($attrs.type === 'date-picker' || $attrs.type === 'time-picker') {
return $attrs.type + '.html';
}
},
controller: function ($scope, $element, $attrs) {
if ($attrs.type === 'date-picker') {
console.log('date-picker');
}
else if($attrs.type === 'time-picker') {
console.log('time-picker');
}
}
}
});
This works well as long as there are no other input elements in the page.
If I put
<input type="time-picker">
<input type="date-picker">
it works fine. Now if I add
<input type="text">
the whole page hangs.
See my fiddle here: http://jsfiddle.net/pWc3K/8/
If you change your html to this:
<input type="text" time-picker> <input type="text" date-picker>
Then you could wire up your directives based on those attributes like so:
app.directive('timePicker', function(){
return {
restrict: 'A',
...
}
});
app.directive('datePicker', function(){
return {
restrict: 'A',
...
}
});
Putting time-picker/date-picker in an input type for an element isn't really valid. If you read up on the angular docs for directives you'll find a whole list of the different things you can associate on. The four ones are:
E - Element name: <my-directive></my-directive>
A - Attribute: <div my-directive="exp"> </div>
C - Class: <div class="my-directive: exp;"></div>
M - Comment: <!-- directive: my-directive exp -->
Try and understand In AngularJs custom directives created can have following restrictions:
The restrict option is typically set to:
'A' - only matches attribute name
'E' - only matches element name
'C' - only matches class name
‘M’ – only comments
These restrictions can all be combined as needed:
for eg:
'AEC' - matches either attribute or element or class name

Categories

Resources