Angular directive inside ng-repeat not two-way binding - angularjs-directive

I have a directive that simulates a simple checkbox with images:
movieApp.directive("imageCheckbox",
function()
{
return {
restrict: "E",
scope: { ngModel: '=' },
template:
'<div ng-switch on="ngModel"> \
<div ng-switch-when="true"> \
<img src="/Content/Images/CheckTrue.png" ng-click="onClick()"> \
</div> \
<div ng-switch-default> \
<img src="/Content/Images/CheckFalse.png" ng-click="onClick()"> \
</div> \
</div>',
link: function(scope, element, attrs)
{
scope.onClick = function()
{
scope.ngModel = !scope.ngModel;
};
}
};
});
This works fine outside of a ng-repeat. However, inside of a ng-repeat it won't two-way bind to the ngModel.
The ng-repeat is inside a table, something like this:
<tr class="movie-info-row" ng-repeat="movie in movies">
<div class="movie-checkbox">
<image-checkbox ng-model="isSelectedToDownload" ng-click="onSelectToDownloadClick(movie.RefId)" />
</div>
There is a lot more inside the table row but that is not relevant here.
The onSelectToDownloadClick handler works and gives me the correct movie.RefId but the isSelectedToDownload flag on my scope is not updated. It is updated when outside of the ng-repeat.
Any ideas?

I finally figured this out and maybe it's useful to others as well:
The ng-repeat directive creates its own child scope which prototypically inherits from the parent scope.
By assigning scope.ngModel = !scope.ngModel; in the onClick function I actually create a new variable ngModel on the child scope, thus and thereby hiding the parent's ngModel variable and disabling the two-way binding.
Solution? There are a few.
I went for controller-as syntax (setting controller to vm) and then invoke like this:
<image-checkbox ng-model="vm.isSelectedToDownload" ng-click="onSelectToDownloadClick(movie.RefId)" />
I cannot change prototypically inherited variables themselves; however I can change their properties.

Related

AnguarJs: ng-repeat only work after clicked button inside directive

I want to bind an array (customLayers) and use it for ng-repeat.
I fill the array inside the kv.colorMap Object.
I have three directives using these technique. But the directive updates the binded array on view ONLY after pressing a functionless button (checkResult), which is inside this directive.
Directive Template Code:
...
<div class="createInfo colorExprContainer">
<div ng-repeat="layer in customLayers">{{layer.color}}</div>
</div>
<div class="buttonWrapper text-center">
<button class="btn" ng-click="checkResult()">Ergebnis prüfen</button>
</div>
...
Directive JavaScript Code:
app.directive('boolKv', function($parse, $timeout){
return {
restrict: 'E',
replace:true,
scope:true,
templateUrl: "directives/boolKV/boolKV.html",
link: function($scope, $element, $attr) {
...
var kv = new BAKV({target: cv[0].id, expr: expr});
$scope.customLayers = kv.colorMap.layers;
...
$scope.checkResult = function(){console.log("it works!");};
});
Does someone have an idea?
Thank you very much!
Thank you MirMasej!
You were right, I was calling it after render. Maybe because I've used the EaselJs Library for canvas. I wanted to get the update after click on a block inside this canvas.
I solved it by adding, if someone has a better idea I would try it:
kv.colorMap.onChangedLayer = function(layer) {
$timeout(function(){
$scope.$apply();
});
};
Im calling this onChangedLayer event after changing the data inside the colorMap object.

How to code optional attribute without value to show/hide some blocks?

How to code optional attribute without value to show/hide some blocks?
Demo
For example, when the "showsum" attribute exists in the line below:
<div ng-controller="myCtrl" showsum headers="['Table Header 1', 'Table Header 2']">
I want to show this line (ex: Sum: 12)
<td ng-show="showsum">Sum: {{ getCol1Sum() }}</td>
well since the ng-show directive takes in an expression, you cannot use it the way you did there,
The ngShow directive shows or hides the given HTML element based on
the expression provided to the ngShow attribute.
i dont know what your reason is to define it as an attribute, but what you can do is create a directive
like so
myApp.directive('showsum ', function() {
return {
restrict: 'A', // restrict to an attribute so we can use it as such
link: function(scope, element, attrs) {
scope.showsum = true; // set the show sum expression so we can access it in the scope
}
}
})
example:
http://plnkr.co/edit/mE5LrSMWdIwPRazEdD3b?p=preview
it will create a showsum attribute for the scope and you can do w.e you want with it

Pass element using ng-show AngularJS

HTML
<li ng-show="sample($event)" TestLi</li>
Javascript
$scope.sample = function($event){ //$event is undefined
//do something
}
I've only tried passing the html element using ng-click but is there a way to pass it using ng-show?.
The attribute ng-show, ng-hide and ng-if usually evaluate expression and not function. You can read about it here:
https://docs.angularjs.org/api/ng/directive/ngShow
However, if you really wish to get the target element, you may try writing a very simple directive.
<li ng-show="sample($event)" getTarget> TestLi</li>
app.directive("getTarget", function() {
return {
link: function(scope, element, attrs) {
console.log(element);
}
}
});

How to create a separate scope isolated from ng-repeat in Angular?

I am new to AngularJS and have some trouble understanding the concept of scope in Angular. I have read some posts on stackoverflow as well as online articles, which advise me to create a custom directive to create an isolate scope, but I am getting nowhere...
As for the project I'm working on, I am trying to make a button that when clicked, will trigger a textarea. However, because of ng-repeat, the textarea is triggered for all buttons while I click only one.
My .js file:
angular.module('myApp')
.controller('myCtrl', function ($scope, Question) {
scope.visible = false;
scope.toggle = function() {
scope.visible = !scope.visible;
};
.directive("myDirective", function () {
return {
scope: {
ngClick: '&',
ngShow: '&'
}
}
});
Here is my HTML file:
<ul>
<li ng-repeat="object in objectList">
<button type="text" myDirective ng-click="toggle()">Click</button>
<textarea myDirective ng-show="visible"></textarea>
</li>
</ul>
Angular is creating child (NOT isolated) scope when ng-repeating, try this out, when you ng-init a variable, it is only visible within that repeat div.
<div ng-repeat="i in [0,1,2,3,4,5,6,7,8,9]" ng-init="visible=false">
<button ng-click="visible=!visible">Toggle</button>
<h1 ng-show="visible">look at me!</h1>
</div>
Plunker
There is no need to use a directive. You need to use object in the foreach to refer each item in the loop.
Add visible to each object in objectList:
$scope.objectList = [
{ visible: false },
{ visible: false },
{ visible: false }
];
Then the toggle button will need to pass the object to toggle:
$scope.toggle = function (object) {
object.visible = !object.visible;
};
The ng-show will need to check object.visible and ng-click will need to pass the object:
<button type="text" ng-click="toggle(object)">Click</button>
<textarea ng-show="object.visible"></textarea>
Plunkr

How to specify model to a ngInclude directive in AngularJS?

I would like to use the same HTML template in 3 places, just each time with a different model.
I know I can access the variables from the template, but there names will be different.
Is there a way to pass a model to the ngInclude?
This is what I would like to achieve, of course the attribute add-variable does not work now. Then in my included template, I would acces the detailsObject and its properties.
<pane title="{{projectSummary.ProjectResults.DisplayName}}">
<h2>{{projectSummary.ProjectResults.DisplayName}}</h2>
<ng-include src="'Partials/SummaryDetails.html'" init-variable="{'detailsObject': projectSummary.ProjectResults}"></ng-include>
</pane>
<pane title="Documents" header="true"></pane>
<pane ng-repeat="document in projectSummary.DocumentResults" title="{{document.DisplayName}}">
<h2>{{document.DisplayName}}</h2>
<ng-include src="'Partials/SummaryDetails.html'" add-variable="{'detailsObject': document}"></ng-include>
</pane>
<pane ng-repeat="header in [1]" title="Languages" header="true"></pane>
<pane ng-repeat="language in projectSummary.ResultsByLanguagePairs" title="{{language.DisplayName}}">
<h2>{{document.DisplayName}}</h2>
<ng-include src="'Partials/SummaryDetails.html'" add-variable="{'detailsObject': language}"></ng-include>
</pane>
If I took a bad approach with using ng-include, is there something else I should try?
There is a rather simple solution, although I must admit, it's not what Misko would recommend. But if creating a directive is an overkill for you and getting Brice's patch is not feasible then the following will help you.
<div ng-repeat="name in ['A']" ng-include="'partial.html'"></div>
<div ng-repeat="name in ['B']" ng-include="'partial.html'"></div>
<script type="text/ng-template" id="partial.html">
<div>{{ name }}</div>
</script>
It's quite evident why it works. See an example here: http://jsfiddle.net/Cndc6/4/
NOTE: this is not my original answer but this is how I'd do this after using angular for a bit.
I would create a directive with the html template as the markup passing in the dynamic data to the directive as seen in this fiddle.
Steps/notes for this example:
Define a directive with markup in the templateUrl and attribute(s) used to pass data into the directive (named type in this example).
Use the directive data in the template (named type in this example).
When using the directive in the markup make sure you pass in the data from the controller scope to the directive (<address-form type="billing"></address-form> (where billing is accessing an object on the controller scope).
Note that when defining a directive the name is camel cased but when used in the markup it is lower case dash delimited (ie it's named addressForm in the js but address-form in the html). More info on this can be found in the angular docs here.
Here is the js:
var myApp = angular.module('myApp',[]);
angular.module('myApp').directive('addressForm', function() {
return {
restrict: 'E',
templateUrl: 'partials/addressform.html', // markup for template
scope: {
type: '=' // allows data to be passed into directive from controller scope
}
};
});
angular.module('myApp').controller('MyCtrl', function($scope) {
// sample objects in the controller scope that gets passed to the directive
$scope.billing = { type: 'billing type', value: 'abc' };
$scope.delivery = { type: 'delivery type', value: 'def' };
});
With markup:
<div ng-controller="MyCtrl">
<address-form type="billing"></address-form>
<address-form type="delivery"></address-form>
</div>
ORIGINAL ANSWER (which is completely different than using a directive BTW).
Note: The fiddle from my original answer below doesn't appear to work anymore due to an error (but keeping it here in case it is still useful)
There was a discussion about this on the Google Group you can see it here.
It looks like this functionality is not supported out of the box but you can use Brice's patch as described in this post.
Here is the sample code from his jsfiddle:
<script id="partials/addressform.html" type="text/ng-template">
partial of type {{type}}<br>
</script>
<div ng-controller="MyCtrl">
<ng-include src="'partials/addressform.html'" onInclude="type='billing'"></ng-include>
<ng-include src="'partials/addressform.html'" onLoad="type='delivery'"></ng-include>
</div>
There is a pull to fix this but it looks like it's dead:
https://github.com/angular/angular.js/pull/1227
Without modifying the Angular source code this will solve the problem in a reusable not-too-hacky-feeling way:
directive('newScope', function() {
return {
scope: true,
priority: 450,
};
});
And an example:
<div new-scope ng-init="myVar = 'one instance'" ng-include="'template.html'"></div>
<div new-scope ng-init="myVar = 'another instance'" ng-include="'template.html'"></div>
Here is a Plunker of it in action:
http://plnkr.co/edit/El8bIm8ta97MNRglfl3n
<div new-scope="myVar = 'one instance'" ng-include="'template.html'"></div>
directive('newScope', function () {
return {
scope: true,
priority: 450,
compile: function () {
return {
pre: function (scope, element, attrs) {
scope.$eval(attrs.newScope);
}
};
}
};
});
This is a directive that combines new-scope from John Culviner's answer with code from Angular's ng-init.
For completeness, this is the Angular 1.2 26 ng-init source, you can see the only change in the new-scope directive is the addition of scope: true
{
priority: 450,
compile: function() {
return {
pre: function(scope, element, attrs) {
scope.$eval(attrs.ngInit);
}
};
}
}
Quick'n'dirty solution:
<div ng-init="details=document||language||projectSummary.ProjectResults">
I hear you! ng-include is not that reusable because it has access to the global scope. It's a little weird.
There should be a way to set local variables. Using a new directive instead of ng-include is a cleaner solution.
The ideal usage looks like:
<div ng-include-template="'Partials/SummaryDetails.html'" ng-include-variables="{ 'detailsObject': language }"></div>
The directive is:
.directive(
'ngIncludeTemplate'
() ->
{
templateUrl: (elem, attrs) -> attrs.ngIncludeTemplate
restrict: 'A'
scope: {
'ngIncludeVariables': '&'
}
link: (scope, elem, attrs) ->
vars = scope.ngIncludeVariables()
for key, value of vars
scope[key] = value
}
)
You can see that the directive doesn't use the global scope. Instead, it reads the object from ng-include-variables and add those members to its own local scope.
It's clean and generic.