I may be a bit confused about scope (I've read a lot of tutorials about it, but at this point I don't understand how to make this thing), anyway, this is what I'm trying to do:
I have a table with various cells
<table my-table>
<tr>
<td my-table-cell>
<span ng-if="!myTableCellStatus.editing">Bla 1</span>
<input ng-if="myTableCellStatus.editing" type="text" name="value" value="Bla 1">
</td>
<td my-table-cell>
<span ng-if="!myTableCellStatus.editing">Bla 2</span>
<input ng-if="myTableCellStatus.editing" type="text" name="value" value="Bla 2">
</td>
</tr>
<tr>
<td my-table-cell>
<span ng-if="!myTableCellStatus.editing">Bla 3</span>
<input ng-if="myTableCellStatus.editing" type="text" name="value" value="Bla 3">
</td>
<td my-table-cell>
<span ng-if="!myTableCellStatus.editing">Bla 4</span>
<input ng-if="myTableCellStatus.editing" type="text" name="value" value="Bla 4">
</td>
</tr>
</table>
And my-table-cell directive
angular.module('myApp').
directive('myTableCell', [
->
require: '^myTable'
restrict: 'AC'
scope: {}
link: ($scope, element, attrs, myTable) ->
# XXX: I have some code here that allows me to access the cell from myTable
# I change the status from there
myTable.addCell(
changeStatus: (status) ->
$scope.$apply ->
$scope.myTableCellStatus.editing = status
cell: element
)
$scope.myTableCellStatus =
editing: false
])
Now, my idea was: make my-table-cell a directive with isolated scope. Then access it in its child to show/hide various elements.
Is this possible? In my code, looks like I can't in any way bind to any value of my-table-cell scope, probably because it's isolated.
Considering I'm confused, a code example about it would be lovely, I initially thought this would be the right approach, but now looks like I'm completely wrong.
Isolate scope is good most of the time, but in your case it won't help. If you want to have HTML inside your directive and you want to expose custom properties to those HTML elements (e.g. myTableCellStatus) - and since this HTML is not coming from your directive's template - you need a "normal" (i.e. non-isolate) scope:
app.directive('myTableCell', function () {
return {
...
scope: true,
link: function postLink(scope, element, attrs, myTable) {
...
scope.myTableCellStatus = {editing: false};
}
};
});
See, also, this short demo.
When you define an isolated scope for your directive, you are defining a private sandbox for your scope variables. Variables defined or referenced from the isolated scope will not use scope inheritance to resolve bindings.
To fix this, you can either pass in your models into your isolated scope from your parent scope through the element attributes. In your directive definition:
scope: {
myTableCellStatus: '='
}
This sets up a two-way model binding between the myTableCellStatus variable resolved on parent scope and the scope variable on your isolated scope with the same name.
Or you can reference $parent inside your link function:
$scope.$parent.myTableCellStatus
This works because you are referencing the parent scope from your isolated scope, which does use scope inheritance to resolve bindings.
[EDIT]
It might be obvious now that if you reference $parent, you are reaching out from your isolated scope and into your parent scope, which if shared will bind to the same model across all your directives. This would work if you're inside a an ng-repeat, since it creates a child scope. But if you're not inside a child directive, the model would be shared.
Related
I am binding an ng-model to an input, but the value of the variable it's bound to is not being updated outside of the div where that directive is declared:
<div input-field
ng-if="startTypes.selected.value == 'LocalDate' || startTypes.selected.value == 'LocalDateTime'">
<input id="date" type="text" ng-model="date" input-date>
<label for="date">Date</label>
Date inner scope: {{date}}
</div>
Date outer scope: {{date}}
When selecting a new date, ony the inner date is updated. The outer one remains with the old value (which might be either undefined or not depending if I declared it in the controller, it doesn't matter).
I am using angular-materialize, I am not sure if this is the source of the issue but it doesn't make sense because it is a specific framework for angular to work with the CSS framework materializecss.
This is the component I am using.
Edit:
I have tried declaring date in the controller as $scope.date = new Date() and indeed the current date is loaded in the date picker. However when a date is selected and the model changes, it's only updated locally (inner scope), while in the outer scope the old value remains.
As ng-if creates a child scope which is Prototypically inherited from its current scope while inserting inner template to DOM, hence in this case ng-model getting created inside ng-if's child scope. So what happening is while creating a child scope it carries the primitive datatype values & reference(object) datatypes values to child scope, thats why you can see the outer scope date is getting value inside ng-if date field(only first time). But when you update the value in date you will not see the value gets updated to outer scope. Because the way child scope has create primitive type value not carry their references, where as objects are carried with their references. So you can create a object like $scope.model = {} & then define a property into it, that will work. Because object are carried with their references to child scope, updating inner object would sync the outer object as well(they both are same). This rule is called as Dot Rule by which you can fix your issue.
$scope.model = {};
$scope.model.date = new Date();
More convenient way to avoid such kind of scope hierarchy is using controllerAs pattern while using controller on HTML. In this case you shouldn't be using $scope instead you will bind all the properties to controller function context (this). Thereafter when using controller you can use alias of controller to get the values of controller like ng-controller="myCtrl as vm"(here vm is alias of controller which has all information binding to this)
HTML
<div input-field
ng-if="vm.startTypes.selected.value == 'LocalDate' || vm.startTypes.selected.value == 'LocalDateTime'">
<input id="date" type="text" ng-model="vm.date" input-date>
<label for="date">Date</label>
Date inner scope: {{vm.date}}
</div>
Date outer scope: {{vm.date}}
You should use an object with a property when binding to ngModel.
$scope.form = {
date: new Date()
};
ngIf like ngRepeat directive creates its own $scope.
So you could use ng-show instead of ng-if in this specific case.
From docs:
The ngIf directive removes or recreates a portion of the DOM tree
based on an {expression}. If the expression assigned to ngIf evaluates
to a false value then the element is removed from the DOM, otherwise a
clone of the element is reinserted into the DOM.
Example:
angular.module('app', [])
.controller('mainCtrl', function($scope) {
$scope.testa = false;
$scope.testb = false;
$scope.testc = false;
$scope.testd = false;
});
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.min.js"></script>
</head>
<body ng-controller="mainCtrl">
Test A: {{testa}}
<br /> Test B: {{testb}}
<br /> Test C: {{testc}}
<br /> Test D: {{testd}}
<br />
<div>
testa (without ng-if):
<input type="checkbox" ng-model="testa" />
</div>
<div ng-if="!testa">
testb (with ng-if):
<!-- if you don't use $parent testb isn't updated -->
<input type="checkbox" ng-model="$parent.testb" />
</div>
<div ng-show="!testa">
testc (with ng-show):
<input type="checkbox" ng-model="testc" />
</div>
<div ng-hide="testa">
testd (with ng-hide):
<input type="checkbox" ng-model="testd" />
</div>
</body>
</html>
I've been looking all over the internet for something like this and I still can't find the answer.
I have a directive that is reused throughout my application, it allows the user to sort and search through lists of items. I have multiple kinds of items that can be passed in to the directive (html templates that I pass in) as well as multiple uses for those templates. I.e, sometimes I want a certain button on that template, but sometimes I want another. (This will make more sense in a minute).
Therefore I have created multiple directives with transclusion in order to achieve this. However, I'm having serious issues with scoping and I can't seem to figure out exactly how to pass the isolated scope to the child directive.
Below is my code:
Item List Directive
var app = angular.module('main');
app.directive('itemList', function(){
var linkFunction = function (scope, element, attributes, ctrl, transclude) {
//Things that modify the scope here. This scope is what I want to pass down to the child directives
//NOTE: I do not currently have a working transclude function which is why I didn't include it here because I have no idea what to do with it
scope.pagedItems = groupItemsToPages(items);
scope.currentPage = 0;
};
return {
restrict: 'E',
replace: 'true',
transclude: true,
templateUrl: 'partials/directives/item-list.html',
link: linkFunction,
scope: {
searchPlaceholder: "#",
items: "=",
control: "="
}
};
});
item-list.html
<div class="form-group">
<!-- I won't put all of the html here, just some to show you what i'm going for -->
<div class="search-field">
<input type="text" ng-model="query.value" placeholder="{{searchPlaceholder}}/>
</div>
<table class="table table-hover">
<tbody>
<tr ng-repeat="item in pagedItems[currentPage]">
<td ng-transclude></td>
</tr>
</tbody>
</table>
</div>
Here's the directive that simply returns the URL of whatever template is passed to it. This is so that I can add in an extra html through further nested transclusions.
item-template.js
var app = angular.module('main');
app.directive('itemTemplate', function() {
return {
restrict: 'AE',
replace: 'true',
transclude: true,
templateUrl: function(tElement, tAttrs){
return tAttrs.templateUrl;
}
};
});
Here's an example template (extremely simplified again, just to show you the layout)
profile-template.html
<div>
<p>item.name</p>
<p>item.description</p>
</div>
<div ng-transclude></div>
Here's an example of the HTML that calls this code
tab.html
<div class="tab">
<div class="available-items">
<item-list control="profileControl" search-placeholder="Search Profiles" items="profileControl.profiles">
<item-template template-url="partials/profile-template.html">
<button type="button" ng-click="launchProfile(item.id)">Launch Profile</button>
</item-template>
</item-list>
</div>
</div>
So now that you've seen the code. The issue I'm having is that my profile-template.html inclusion isn't getting the scope from the directive above it even though I've tried cloning the scope to it. All the examples I've seen require you to remove the template key from the directive and assume you're only returning the code you have in your transclusion. In my case, I have other html that I want to display in the template.
Any help would be appreciated
Instead of trying to pass the scope between your directives, you can make use of the $parent attribute and get access to higher scopes.
For instance, in your item-template controller you could gain access to the higher scope from item-list with a code like this:
if ($parent.searchPlaceholder === '') {
...
}
In order give feedback on the validity in my input forms I'm using ng-class. These statements will look something like:
<div ng-class="{ 'has-error': !frmSomeName.vcHeader.$valid && frmSomeName.vcHeader.$dirty }">
<input type="text" name="vcHeader" ng-model="model.someText" ng-minlength="10" required />
</div>
I dislike the lengthiness of the statement, and would like to replace it with something alike:
<div validation-state="frmSomeName.vcHeader">
<input type="text" name="vcHeader" ng-model="model.someText" ng-minlength="10" required />
</div>
In order to avoid having to duplicate ngClass' behavior I'd like the the directive to add the ng-class directive.
This plnkr demonstrates my attempt at adding the attribute, and although it works in the simplest scenario, it is faulty and will not function with transclusion (or other more complex directives).
I know it doesn't work because of the misuse of the compile and link stages, however I'm not sure on how to actually make it work properly. Therefore my question: How do I add different directive-attribute from a directive-attribute?
The elements within the transclude directive aren't receiving the same scope. If you use the angualr $compile method and apply the scope of the transclude directive to the scope of the child directives it should work. The following should be added to your simpleTransclude directive:
link: function(scope, element) {
$compile( element.contents() )( scope )
}
remember to pass $compile into the directive.
I've forked your plnkr and applied the simpleTransclude scope to it's contents.
I have the following
<table id="socialMediaContainer" class="socialMediaContainer" style="width: 100%;">
<tbody>
<tr ng-repeat="row in detailCollection.ChannelsInfo"
ng-controller="WhiteLabelSitesCtrl">
<td><input id="txtSocialName" type="text" class="socialName"
placeholder="Name" ng-disabled="ViewMode" maxlength="250"
value="{{row.SocialChannelName}}" /> </td>
<td><input id="txtSocialURL" type="text" class="txtLabel socialURL"
placeholder="URL" ng-disabled="ViewMode" maxlength="250"
value="{{row.SocialChannelURL}}" />
</td>
<td class="DragnDropIcon"></td>
<td>
<a class="orange " ng-show="ViewMode">Upload</a></td>
</tr>
</tbody>
</table>
and I have another button outside the ng-repeat that updates the ViewMode variable, but this is not working inside the ng-repeat neither for the ng-show not the ng-disabled. what am i missing here?
The problem seems to be, that you need to move ngController directive to the table level (at least): it can't be on the same element with ngRepeat if the later iterated over the array defined in controller.
<table ng-controller="WhiteLabelSitesCtrl" ... >
<!-- ... -->
</table>
Demo: http://plnkr.co/edit/tu4TLmWIxdcYaiEd7whn?p=preview
ng-repeat creates a childscope for each item in the repeater.
Thus viewmode will be a primitive value on that child scope and therefore as a primitive will lose inheritance binding with the parent scope.
If you declare it as an object property in the controller scope however it will then be a reference to that parent object.
$scope.mode ={ViewMode: false}
html example
<a class="orange " ng-show="mode.ViewMode">Upload</a></td>
Try to pass object instead variable inside ng-repeat scope. Instead ViewMode,
declare in your controller:
$scope.model = {};
$scope.model.ViewMode = false;`.
And your binding must be like this: <a class="orange " ng-show="model.ViewMode">. After that all be work fine.
Read this article to understand why it happens: https://github.com/angular/angular.js/wiki/Understanding-Scopes
The button outside of the ng-repeat is not going to be nested in the same controller. The variable that it's modifying probably is not the same one that ViewMode under the WhiteLabelSitesCtrl is looking at.
When you point to this ng-controller, that controller will be activated with a new scope associated with it
<div ng-repeat ng-controller="WhiteLabelSitesCtrl">
<div ng-show="someValue"></div>
</div>
When you reference this controller again on another tag, it won't reference the existing controller as you might be expecting, it'll actually do the exact same thing... it will create the controller, and create a new scope for it- completely separate from the original one.
<div ng-controller="WhiteLabelSitesCtrl">
<button ng-click="someValue = !someValue"></button>
</div>
I am leaning AngularJS, specifically working on learning directives. Would like to have a form input directive that I can re-use on all my forms to encapsulate all the boiler plate markup. Though I am having trouble getting two way binding to work in my directive. It is using an isolated scope with its own internal property to store the value of the input field. I've setup a watch on that internal property that is correctly pushing the value from within the isolate scope up to the controllers scope. What I am trying to figure out is how to take an initial value from the controllers scope and set it as the initial value in my directive.
Plunker Link: http://embed.plnkr.co/TbVB0q9DHhBCVLQ4U64W/script.js
Typing in the first input box changes the controller scopes property, but not the directives value. Typing in the second input box changes the directive and the controller property.
I know this is possible using an attribute to pass the initial value. However I am hoping to be able to extract the value from the controller scopes property via the ngModel reference in my directive.
EDIT AFTER ANSWER:
For those not sure about why you want to go through the effort of learning directives. Especially when Angular is so powerful even without using directive. This is one good reason why.
Input fields on my form without directive:
<div class="form-group">
<label for="firstName" class="col-sm-6 col-md-4 control-label">First Name</label>
<div class="col-sm-6 col-md-8" ng-class="{'has-error': userForm.firstName.$invalid}">
<input type="text" id="firstName" name="firstName" placeholder="First Name" ng-model="muState.currentUser.firstName" class="form-control" required
popover="Cannot be blank" popover-trigger="{{{true: 'mouseenter', false: 'never'}[userForm.firstName.$invalid]}}" />
</div>
</div>
After using my directive:
<ws-form-input input-name="firstName" input-label="First Name" input-placeholder="First Name"
ng-model="muState.currentUser.firstName"
required input-error="Cannot be blank"></ws-form-input>
Go through the effort. You'll be trading the headaches of learning versus a maintenance nightmare later.
I think you can simplify your directive altogether by using the isolated scope's '=' attribute notation.
Something like this:
JAVASCRIPT
app.directive('inputDirective',function(){
return {
restrict: 'E',
replace: true,
scope: {ngModel: '='},
templateUrl: "directiveTemplate.html",
require: '^form',
link: function(scope, elem, attrs, ctrl){
scope.form = ctrl;
scope.required = false;
// If attribute required exists
if (attrs.required !== undefined) {
// ng-required takes a boolean which is read from this scope variable
scope.required = true;
}
}
};
});
HTML DIRECTIVE
<div>
<input type="text" id="directiveInput"
ng-model="ngModel" class="form-control" ng-required="required"/>
<br/>
Isolated Scope value of the input box: {{ngModel}}
</div>
See UPDATED PLUNKER
You have to implement ngModel.$render, see ref here.
The following code in your directive will suffice:
scope.ngModel.$render = function() {
scope.inputBinding = scope.ngModel.$viewValue;
};