Angular Directive Two-Way Binding Issue - angularjs

I am creating a custom input directive for some added features.
app.directive('myTextInput', [
function () {
var directive = {};
directive.restrict = 'A';
directive.scope = {
textModel: '='
};
directive.link = function ($scope, element, attrs) {
// FUnctionality
};
directive.template = '<input ng-model="textModel" type="text">';
return directive;
}]);
and used the isolated scope property textModel for two way binding.
And the parent scope HTML is:
<div my-text-input text-model="myScopeVariable"></div>
<span>TYPED : {{myScopeVariable}}</span>
The controller scope has variable $scope.myScopeVariable=""; defined.
When i do this.
what ever typed in the directive input is able to print in the <span> tag.
Which tells it is updating.
The issue is.
in the parent scope. i have a method that will execute on a button click.
$scope.doSomething = function(){
console.log($scope.myScopeVariable);
};
On click on the button. the log is empty. which is supposed to be what is typed in the directive input.
THIS IS STRANGE
Now if define the scope variable as
$scope.myScopeVariable = {key:''};
and use in HTML as
<div my-text-input text-model="myScopeVariable.key"></div>
<span>TYPED : {{myScopeVariable.key}}</span>
then it is working every where. Even in the previously said function doSomething().
Any idea what happening here ?

This is happen because myScopeVariable contain value as String.
So if you change the value of from textbox of directive. It wont be reflected.
And in second method, you are referring Object. so whenever you change value of object , its relevant watch method is called. And value will be updated.

my-text-input is a directive, so he has his own scope, so the model myScopeVariable is not watch by the parent.
try with this :
<div my-text-input text-model="$parent.myScopeVariable"></div>
<span>TYPED : {{myScopeVariable}}</span>
It's not the best practice but it might works.

Related

How to include data/scope from controller in a dynamically added directive?

I'm trying to figure out how to include scope with a directive that I add to the dom on a click event in a controller.
Step 1. On a click event, I call a function in my controller that adds a directive like this
$scope.addMyDirective = function(e, instanceOfAnObjectPassedInClickEvent){
$(e.currentTarget).append($compile("<my-directive mydata='instanceOfAnObjectPassedInClickEvent'/>")($scope));
}
//I'm trying to take the `instanceOfAnObjectPassedInClickEvent` and make it available in the directive through `mydata`
The above, part of which I got from this SO answer, successfully adds the directive (and the directive has a template that gets added to the dom), however, inside the directive, I'm not able to access any of the scope data mydata it says it's undefined.
My directive
app.directive('myDirective', function(){
return {
restrict: 'AE',
scope: {
mydata: '='
//also doesn't work if I do mydata: '#'
},
template: '<div class="blah">yippee</div>',
link: function(scope,elem,attrs) {
console.log(scope) //inspecting scope shows that mydata is undefined
}
}
}
Update
I changed the name of datafromclickedscope in the OP to make it more clear. In the controller action addMyDirective (see above) instanceOfAnObjectPassedInClickEvent is an instance of an object passed into the controller method on a click event that I try to pass into the directive as mydata='instanceOfAnObjectPassedInClickEvent'. However, even if I change = to # in the directive and I try to access scope.mydata in the link function of the directive, it just shows a string like this "instanceOfAnObjectPassedInClickEvent", not the actual object data that is available to me in my method that handles the click event
When you use mydata='instanceOfAnObjectPassedInClickEvent' in a template you need instanceOfAnObjectPassedInClickEvent to defined in $scope. So before compiling you should assign a variable in $scope. I will rename this variable in code below, so that same names would not confuse you and it would be clear that a formal parameter of a function cannot be visible in a template.
$scope.addMyDirective = function(e, instanceOfAnObjectPassedInClickEvent){
$scope.myEvent = instanceOfAnObjectPassedInClickEvent;
$(e.currentTarget).append($compile("<my-directive mydata='myEvent'/>")($scope));
}
EDIT: slightly adapted jsfiddle not using JQuery no manipulate DOM

Template in child directive has different scope than the child directive's link function

Here is a non-functioning Plunkr with the relevant code:
http://plnkr.co/edit/cM67vJaL0gjma0SoPbK2?p=info
Why does the "pullThirdPartyCollection.html" template have a different scope than the pullThirdPartyCollection directive's link function? I have to use ng-model="$parent.$parent.bggUsername" to make sure two way binding happens correctly. Otherwise, scope.bggUsername is null in the link function's scope.pullCollection function.
If I set an initial value in the link function: scope.bggUsername = 'test', that initial value will display on the screen. Even when I change the value in the input box, and I have an output value {{bggUsername} right next to it, that value changes too. However, when I click the search button and the pullCollection function, inside the link function is called, the scope inside the link function does not have the value I expect for bggUsername.
Also, the pullThirdPartyCollection link function's scope's $id is 20. When I print the value of $id in the template pullThirdPartyCollection.html.js, the value of $id is 33. That is how I figured out I could change the link functions bggUsername by calling $parent.$parent.bggUsername in the template. Still, it does not make sense why the template and the link function have two different scopes. Any ideas what I am I doing wrong?
Here is some of the basic setup
<div paginated-search query="query" total-items="totalItems" current-page="currentPage" items-per-page="itemsPerPage" search-on-load="searchOnLoad" urls="urls" select-item="selectItem(item)" get-search-types="getSearchTypes()" show-search-types="showSearchTypes" selected-search-type="selectedSearchType" edit-item="editItem(e,id)" search-button-id="search-items-button">
<div pull-third-party-collection ></div>
</div>
The pullThirdPartyCollection directive:
angular.module('directives.pullThirdPartyCollection', [])
.directive('pullThirdPartyCollection', function($q, urls, KeyCloakService) {
return {
require: '^paginatedSearch',
restrict: 'EAC',
templateUrl: 'pullThirdPartyCollection.html',
scope:{
},
link: function postLink(scope, element, attrs, paginatedSearchCtrl) {
scope.pullCollection = function(e) {
var deferred = $q.defer();
$(e.currentTarget).button('loading');
KeyCloakService.makeRequest(urls.collectionserviceurl + '/pulldata/bgg?username=' + scope.bggUsername + '&collectiontype=trade&urltype=all&subject=' + KeyCloakService.auth.subject,
null,
function(data) {
$(e.currentTarget).button('reset');
scope.pulled = true;
paginatedSearchCtrl.searchItems();
deferred.resolve();
},
null,
'application/json',
'application/json',
'GET');
return deferred.promise;
};
scope.toggleAllow = function(){
scope.allow = !scope.allow;
};
}
};
});
An existing entry in stackoverflow led me in the right direction:Two way binding not working in directive with transcluded scope. I knew AngularJS had a tendency to create child scopes for transcluded directives. I just didn't realize it can create different scopes between the template and the link function. The child scope's bggUsername initially receives itsvalue from the parent. After that a shadow copy is created and is no longer kept in synch with the parent. Apparently the solution is to only bind objects. From the documentation: every ng-model should have a "." in it. I changed the bound property from a primitive to an object and the object stays in synch inside the link function. So the solution was to make bggUsername an object and change ng-model="bggUsername" to ng-model="bggUsername.value".

Value of input field is being removed by directive

I have a simple Angular Problem - I think it's probably a case of can't see the wood for the trees here.
I have an input field with a directive attached. The purpose is eventually to compare new with old data and show a popup. However, as soon as I add the directive attribute to the input field, the value disappears:
Plunk here: http://plnkr.co/edit/BQvKGe6kjuD0ThPBYJ4d?p=preview
HTML:
First Name:
<input type='text' ng-model='currentEditItem.strFirstName' name='strFirstName' id='strFirstName'
cm-popover="currentEditItem.personOldData.strFirstName"/>
<br><br>
ngModel: {{currentEditItem.strFirstName}} <br>
cmPopover: {{currentEditItem.personOldData.strFirstName}}
JS
var app = angular.module('app', []);
app.controller('Ctrl', function ($scope) {
$scope.currentEditItem = {};
$scope.currentEditItem.strFirstName = "Bob";
$scope.currentEditItem.personOldData = {};
$scope.currentEditItem.personOldData.strFirstName = "Roger";
});
app.directive("cmPopover", function () {
return {
scope: {
ngModel: "=",
cmPopover: "="
},
link: function (scope, elem, attrs) {
console.log("ngModel", scope.ngModel);
console.log("cmPopover", scope.cmPopover);
}
}
});
If you go to the Plunk and remove the cm-popover attribute, the input field is filled with the value from the model. When the attribute is added the value disappears although the model is still in the scope with the correct value.
In your directive you declare an isolate scope. This input's scope is now this isolate scope since it's the directive element. It's looking for the currentEditItem object which doesn't exist in the isolate scope
ngModel does not create a new isolated scope for itself so it can $watch without having to hardcode a $parent in it's internal code.
But then you add another directive on the same DOM node that creates an isolated scope for itself. Couple this with the fact that you can only have a single isolated scope on a DOM node and you basically force ngModel to use/work with the same scope cmPopover created.
So when writing ng-model="currentEditItem.strFirstName" you are actually addressing the $scope inside the cmPopover directive, no the one in the (parent) controller. You can check this is the case by using ng-model="$parent.currentEditItem.strFirstName" - and it will work.
There's quite a lengthy conversation here with a lot of possible workarounds and solutions that leads to an actual fix in release 1.2.0.
So long story short: update to at least AngularJS 1.2.0 and this will work.

Directive scope is not refreshing when provided value will be assign to directive scope

I created my own directive in angularjs and I noticed that directive scope is not refreshing when I change scope in main controller.
I made simple example which after 3s changing scope value, but the value is not changed in the directive. The issue exists only if I assign directive provided value to the directive scope.
Entire example is available here: jsfiddle
My directive:
myApp.directive('textPresenter', function() {
return {
transclude : false,
restrict: 'E',
scope: {
adfName : '='
},
template: '{{xxx}}' ,
controller: function($scope){
$scope.xxx = $scope.adfName; //this is the issue
}
};
});
$scope.xxx should refresh, after scope change in main controller.
The scope value adfName in the template is bound to the one in your main controller by Angular, and will change when that one changes. If you change the template to:
template: '{{adfName}}'
as in http://jsfiddle.net/6bp7c/1/ , then you can see this.
However, from your comment, you say you can't do this. Then one possibility would be setup a watcher in your directive, to watch adfName, and set xxx to be equal to it when it changes:
$scope.$watch('adfName', function(newAdfName) {
$scope.xxx = newAdfName;
});
as can be seen at http://jsfiddle.net/7V9FV/ . This is necessary because
$scope.xxx = $scope.adfName
just copies the contents of adfName and put it into xxx. If adfName later changes, then the contents of xxx are unaffected.
I am curious as to why you have to copy the variable though, and not just change the template to use the one passed into the directive.

How to append a directive to another directive which calls a function from controller.

I'm trying to append a directive which occurs when an event is fired via the $watch function in angular. The first directive updater would insert a custom element <confirmation /> into my view. confirmation should have it's respective angular bindings.
after it's inserted I want the function startChange() which resides in my MainCtrl to fire inside the new confirmation directive.
I've made a plnkr http://plnkr.co/edit/BEozKyRZ0rJMz2e0jyp0?p=preview
Any help would be hugely appreciated.
First off...
There shouldnt be a '.' between the (el) and the ($scope)
var ngEl = $compile(el).($scope);
remove the dot and it will fix the app not being defined in the console.
var ngEl = $compile(el)($scope);
Next to get the error in the console of the startChange function not being defined, name space it on the scope of the directive and be sure to pass it into the isolate scope as well.
HTML:
<confirmation message="Please Select something" state="state" startChange="startChange()" class="background-state-{{ state }}">someText</confirmation>
Directive:
scope : {
message : '#',
state: '=',
startChange: '&'
}
In the linker:
scope.startChange();
I'm still not sure if this is the situation you would like as the other directive update isn't being used anywhere that I can see...
Here is your plunker with my changes: http://plnkr.co/edit/8VoJSj7bHUkaEjUFrn3i?p=preview

Resources