Angularjs directive with bi-directional binding not working - angularjs

I'm trying to write a simple directive that allows the user to edit a certain variable, but when i try to update the parent variable it doesn't work.
this is my html:
<p class="scene-field-name editable-name" editable="foo"> {{foo}} </p>
and the directive:
myApp.directive('editable', function ($window, $compile) {
return {
restrict: "A",
template: '<div class="editable-value" ng-hide="editorOn">{{value}} <a class="edit-a" ng-click="editorOn = true">edit</a></div>' +
'<div class="editable-editor" ng-show="editorOn">' +
'<input type="text" value="{{tmpValue}}" />' +
'<a ng-click="setValue()">OK</a>' +
'</div>',
scope: {
value: "=editable"
},
controller: function($scope) {
$scope.tmpValue = $scope.value;
$scope.editorOn = false;
$scope.setValue = function () {
$scope.value = $scope.tmpValue;
$scope.editorOn = false;
}
}
};
here it is in jsfiddle:
http://jsfiddle.net/4srx2z0c/2/
you can see that when clicking OK it doesn't save the value back in the parent scope...

You don't bind the input to tmpValue.
Instead of <input type="text" value="{{tmpValue}}" /> you should have <input type="text" ng-model="tmpValue" />.

Related

Adding ng-model to directive

I have the following directive to reverse geocode a lat & long into a place:
/* global app*/
app.directive('reverseGeocode', function () {
return {
restrict: 'A',
template: '<div ng-model="autoLocation"></div>',
link: function (scope, element, attrs) {
var geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(attrs.lat, attrs.lng);
geocoder.geocode({ 'latLng': latlng }, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[1]) {
element.text(results[1].formatted_address);
} else {
element.text('Location not found');
}
} else {
element.text('');
}
});
},
require: "ngModel",
replace: true
}
});
But for some reason, it doesn't seem to retrieve the ng-model and display it:
<div class="form-group move-down" ng-class="{ 'has-error': place === null }">
<label for="place">Picture taken in:</label>
<input type="text" class="form-control" ng-if="!capture.geo" id="place" ng-model="place.formatted_address" ng-autocomplete options="options" required details="place" ng-click="checkPlace(place)"
uib-tooltip="Please pick the location where you captures your photo!"
tooltip-placement="top-right"
tooltip-trigger="mouseenter"
tooltip-enable="!details.formatted_address"
tooltip-class="tooltip">
// DIRECTIVE USED HERE
<reverse-geocode class="form-control" ng-if="capture.geo" lat="{{capture.latitude}}" lng="{{capture.longitude}}">
<div ng-show="place === null" class="noPlace">
<i class="glyphicon glyphicon-remove"></i> Please fill in a valid location!
</div>
</div>
What my goal is, is to display the location below using:
<div ng-if="capture.geo">Place: {{autoLocation}}</div>
First you need to drop restrict or at least use E (element) instead of A (attribute), since you are using the directive as element.
Second, you don't need to add or pass ng-model, which is a directive itself, to your directive. You can access the controller scope variable capture directly. If you prefer directive to have it's own, isolated scope, then you should two-way bind the controller variable to directive's scope using = (or # if you prefer passing literal strings).
If I understood correctly, below is what you are after (simplified but functionality is there).
HTML
<body ng-controller="MyCtrl">
lat <input type="number" ng-model="capture.lat">
lng <input type="number" ng-model="capture.lng">
<reverse-geocode capture="capture"></reverse-geocode>
</body>
JavaScript
angular.module('app', [])
.controller('MyCtrl', function($scope) {
$scope.capture = {
lat: 10.0,
lng: 20.0
};
})
.directive('reverseGeocode', function () {
return {
restrict: 'E',
scope: {
capture: '='
},
template: '<div ng-bind="autoLocation"></div>',
link: function(scope) {
scope.$watch('capture', function() {
if (scope.capture.lat && scope.capture.lng) {
scope.autoLocation = 'reverse geocode address for [' +
scope.capture.lat + ' ' + scope.capture.lng + '] here';
} else {
scope.autoLocation = 'invalid coordinates';
}
}, true);
},
};
});
It won't work in that way. ng-model only works for input type element where a user can pass some input.
You need to use two-way data communication so that you can modify a scope variable from directive to it's associated scope's variable.

Angular assign value from directive to object

I have this custom directive:
var geo = angular.module('Geo', ['Gealocation']);
function SearchForm($scope){
$scope.location = '';
$scope.doSearch = function(){
if($scope.location === ''){
alert('Directive did not update the location property in parent controller.');
} else {
alert('Yay. Location: ' + $scope.location);
}
};
}
/* Directives */
angular.module('Gealocation', []).
directive('googlePlaces', function(){
return {
restrict:'E',
replace:true,
// transclude:true,
scope: {location:'='},
template: '<input id="google_places_ac" name="google_places_ac" type="text" class="form-control"/>',
link: function($scope, elm, attrs){
var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
$scope.location = [place.geometry.location.lat(), place.geometry.location.lng()];
$scope.$apply();
});
}
}
});
geo.controller('SearchForm', SearchForm);
And in index.html i have few input and custom directive:
<input type="text" class="form-control" ng-model="meeting.topic"></input>
<input type="text" class="form-control" ng-model="meeting.when"></input>
<input type="text" class="form-control" ng-model="meeting.level"></input>
<input type="text" class="form-control" ng-model="meeting.describe"></input>
<google-places location=location></google-places>
<button ng-click="doSearch()" class="btn btn-large btn-primary">Search!</button>
and to display value(location with lat and lng) from directive i can do that by:
{{location}}
But how can i assign this location to something like that :
meeting.location
becouse i need to pass later object meeting
I solved this by:
In controller:
$scope.meeting = {
location: ''
};
In view:
{{meeting.location = location}}

How to access ng-model value in directive?

I created a directive for google map auto-complete. everything is working fine, but the problem is when I need to access the value of input and re-set it. it doesn't work. Here is code:
<div controller='mainCtr'>
<span click='reset(destination)'>Reset</span>
<div class='floatleft' style='width:30%;margin-right:40px;'>
<smart-Googlemaps locationgoogle='destination.From'></smart-Googlemaps>
<label>From</label>
</div>
</div>
In the directive:
angular.module('ecom').directive('smartGooglemaps', function() {
return {
restrict:'E',
replace:false,
// transclude:true,
scope: {
locationgoogle: '='
},
templateUrl: 'components/directives/autocomplete/googlemap-search.html',
link: function($scope, elm, attrs){
var autocomplete = new google.maps.places.Autocomplete($(elm).find("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
// $scope.location = place.geometry.location.lat() + ',' + place.geometry.location.lng();
// console.log(place);
$scope.locationgoogle = {};
$scope.locationgoogle.formatted_address = place.formatted_address;
$scope.locationgoogle.loglat = place.geometry.location;
$scope.locationgoogle.locationText = $scope.locationText;
$scope.$apply();
});
}
}
})
Here is html for directive:
<input id="google_places_ac" placeholder="Please enter a location" name="google_places_ac" type="text" class="input-block-level" ng-model='locationText'/>
The directive works fine, I create a isolated scope(locationgoogle) to pass the information I need to parent controller(mainCtr), now in the mainCtr I have a function calld reset(), after I click this,I need to clean up the input make it empty. How Can I do it?
One way to access the value of the model in your directive from a parent controller is to put that on the isolate scope too and use the two-way binding flag = like you've done with the locationgoogle property. Try this:
DEMO
html
<body ng-controller="MainCtrl">
<button ng-click="reset()">Reset</button>
<smart-googlemaps location-text="locationText"></smart-googlemaps>
</body>
js
app.controller('MainCtrl', function($scope) {
// need to define model in parent and pass to directive
$scope.locationText = {
value: ''
};
$scope.reset = function(){
$scope.locationText.value = '';
}
});
app.directive('smartGooglemaps', function() {
return {
restrict:'E',
replace:false,
// transclude:true,
scope: {
locationgoogle: '=',
locationText: '='
},
// ng-model="locationText.value"
template: '<input id="google_places_ac" placeholder="Please enter a location" name="google_places_ac" type="text" class="input-block-level" ng-model="locationText.value"/>',
link: function($scope, elm, attrs){
// implement directive googlemaps logic, set text value etc.
$scope.locationText.value = 'foo';
}
}
})

How to data-bind an mvc partial view using angularjs directives

I have the following angularjs directive:
app.directive("partnersInfoView", function ($http) {
return {
restrict: 'A',
link: function ($scope, element) {
$http.get("/home/PartnerInfoTab") // immediately call to retrieve partial
.success(function (data) {
element.html(data); // replace insides of this element with response
});
}
};
});
which basically goes to an MVC controller and returns a partial view
public ActionResult PartnerInfoTab()
{
return PartialView("../ManagePartners/PartnerInfoTab");
}
in the parent view i have the following declaration:
<div id="genericController" ng-controller="GenericController">
<div partners-info-view></div>
</div>
that is making use of the angular directive to render the partial view, so far everything works great. Inside of my angular genericController i have a scope variable:
$scope.Data = data;
where data it's a json object that comes as response from a Rest Service
Json Response e.g.
{[
{"firstName":"John", "lastName":"Doe"},
{"firstName":"Anna", "lastName":"Smith"},
{"firstName":"Peter", "lastName":"Jones"}
]}
The issue im having is that i cannot bind the $scope.Data variable in the directive template:
<div id="PartnerInfoTab">
<div class="form-group">
<label class="col-md-2 control-label">Name</label>
<div class="col-md-8">
<input id="txt_name" class="form-control" type="text" ng-model="Data.firstName">
</div>
</div>
</div>
My question is, How do you pass the parent scope to the angular directive in order to be able to data-bind the elements in the partial view. What am i missing ??
Thanks in advance.
I don't see any use of fetching template manually using $http.get and then inserting it to DOM. you can simply give templateUrl value in directive configuration and angular will fetch the template and compile it for you.
app.directive("partnersInfoView", function ($http) {
return {
restrict: 'A',
templateUrl: '/home/PartnerInfoTab',
link: function (scope, element, attr) {
// Do linking
}
};
});
And, your partnersInfoView does not create isolated scope. so, you can access values of partnersInfoView's parent scope value. see the below snippet.
var app = angular.module('app', []);
app.run(function($templateCache) {
// Simulating the fetching of /home/PartnerInfoTab template
var template = '<div id="PartnerInfoTab">' +
'<div class="form-group">' +
'<label class="col-md-2 control-label">Name</label>' +
'<div class="col-md-8">' +
'<input id="txt_name" class="form-control" type="text" ng-model="Data[0].firstName">' +
'<input id="txt_name" class="form-control" type="text" ng-model="Data[1].firstName">' +
'<input id="txt_name" class="form-control" type="text" ng-model="Data[2].firstName">' +
'</div>' +
'</div>' +
'</div>';
$templateCache.put('/home/PartnerInfoTab', template);
});
app.controller('GenericController', function($scope) {
$scope.Data = [{
"firstName": "John",
"lastName": "Doe"
}, {
"firstName": "Anna",
"lastName": "Smith"
}, {
"firstName": "Peter",
"lastName": "Jones"
}];
});
app.directive("partnersInfoView", function($http) {
return {
restrict: 'A',
templateUrl: "/home/PartnerInfoTab",
link: function($scope, element) {
// linking
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div id="genericController" ng-controller="GenericController">
<div partners-info-view></div>
</div>
</div>
What you are missing is probably compilation of your template. What you have to do is use $compile service in your success handler:
.success(function (data) {
var linkingFunction = $compile(data); // compile template
var linkedElement = linkingFunction($scope); // and link it with your scope
element.append(linkedElement);
});

Cannot access attribute that is a directive inside another directive's template

My custom directive is "testapp". I would like to access the value of "file-model" in its template, which would contain the file object to be uploaded. Whatever I do am not able to get the value, its displaying "undefined". Please suggest ways to solve this problem.
app.directive('testapp', function ($compile) {
return {
restrict: 'E',
scope: {
text: '#',
filem: '=fileModel'
},
require:'fileModel',
replace:true,
template: '<div class="col-xs-4" style="padding-left:0px"><div class ="jumbotron" id="Section1"><input type="text" name="id" id="section{{incr}}" placeholder="Section Heading" class="form-control"><form id="addLIForm1"><div ng-repeat="i in range(fileIncr) track by $index"><input type="text" name="file{{incr}}{{$index+1}}" id="file{{incr}}{{$index+1}}" class="form-control" placeholder="File Name" style="width:50%" ><input type = "file" id="path{{incr}}{{$index+1}}" file-model = "file{{incr}}{{$index+1}}"></div></form><a id="addMore" ng-click="addfile()" href="#">Add More File</a><a id="addMoreSec1" ng-click="add()" class="pull-right" href="#" ng-show="showValue">Add More Section</a></div></div>',
controller: function ($scope, $element,$window) {
$scope.incr=$window.globalIncr;
$scope.fileIncr=$window.globalfileIncr;
$scope.showValue=$window.globalsectionValue;
$scope.add = function () {
$window.globalsectionValue=false;
$window.globalIncr+=1;
$window.globalfileIncr=1;
var el = $compile("<testapp></testapp>")($scope);
$element.parent().append(el);
//alert($window.globalfileIncr);
//alert($element.parent().parent());
};
$scope.range=function(n)
{
return new Array(n);
}
$scope.addfile=function(){
alert($scope.filem);
$scope.fileIncr+=1;
$window.counterObject[$scope.incr] = $scope.fileIncr;
};
}
};
});

Resources