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

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);
});

Related

Generate HTML in custom directive and $compile in AngularJS

PLUNKER:
https://plnkr.co/edit/IQpGhinzsHUUqmwbHFmQ?p=preview
I am trying to create a form that reads from some JSON and creates the relevant view. Each set of questions are in their own step. I am having trouble getting access to each input to validate it using AngularJS.
How do I get access to the answers model that is created in the forEach loop?
HTML:
<form ng-app="MyApp" novalidate >
<section class="question step " ng-controller="StepController">
<div class="step-contents">
{{title}}
<step-contents content="content" ></step-contents>
</div>
<button >Prev</button>
<button ng-click="nextStep(content)">Next</button>
</section>
</form>
My AngularJS:
var app = angular.module('MyApp', []);
app.controller('StepController', function($scope) {
$scope.index = 0;
$scope.nextStep = function() {
console.log($scope.answers); // This should be the input data for _THIS_ step only
}
$scope.showStep = function() {
//FOREACH HAPPENS HERE
// Loops over some JSON to generate the following HTML:
$scope.title = "Step 1 title";
var html = '<input type="number" ng-model="answers.amount" />';
html += '<input type="text" ng-model="answers.name" />';
$scope.content = html;
//FOREACH ENDS HERE
}
$scope.showStep();
});
app.directive('stepContents', function ($compile) {
var linker = function(scope, element, attrs){
element.html(scope.content);
$compile(element.contents())(scope);
};
return {
restrict: 'E',
link: linker,
scope: {
content: '=',
},
};
});
Give a name to your form, and then you can access the form elements with $scope.formName.inputName, which will reference the ngModelController of the input. Check out Angular's documentation for ngFormController and ngModelController for more details.

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.

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';
}
}
})

Render a directive from a dynamic text

I'm trying to use a custom directive loaded from a dynamic text, 'till now I've created the directive and if I use this directive in the HTML file works, but what I want to do is use this directive in a model (string).
This is a sample
https://jsfiddle.net/4Lg42e9d/2/
<div ng-app="test">
<div class="holder" ng-controller="MainController">
<div class="question">
<label ng-bind-html="saveHtml">{{saveHtml}}</label><br>
<input type="text" ng-model="current.answer">
</div>
<button ng-click="goNextQuestion()">Next</button>
<hr>
<answer></answer>
<br>
<br>
<div>{{config}}</div>
</div>
</div>
js file:
'use strict';
var app = angular.module('test', ['ngSanitize']);
app.controller('MainController', function($scope, $sce){
var index = 0;
$scope.config = [
{
"id": "uniqueIdOne",
"question": "What is your name?",
"answer" : ""
},
{
"id": "uniqueIdTwo",
"question": "Great <answer></answer>, <strong>this is some random text</strong>.",
"answer": ""
}
];
$scope.goNextQuestion = function(){
$scope.current = $scope.config[++index];
$scope.trustHtml();
}
$scope.trustHtml = function(){
$scope.saveHtml = $sce.trustAsHtml( $scope.config[index].question );
}
$scope.current = $scope.config[0];
$scope.trustHtml();
});
app.directive('answer', function() {
return {
template: 'This is rendered by the directive answer.',
};
});
I can load the text with the directive, but do not render the content.
The question is: How can i trigger the directive render?
Thanks in advance!
You should create a directive to compile the text with the directive inside.
Using $compile service so you can trigger the directive render, like:
JS
app.directive('myDirective',[ '$compile', function($compile){
return {
restrict: 'A',
link: function($scope, iEle, attrs){
var compiledTpl;
function destroyData() {
if (compiledTpl) {
compiledTpl.remove();
compiledTpl = null;
}
if ($scope.$newScope) {
$scope.$newScope.$destroy();
$scope.$newScope = null;
}
}
$scope.$watch(attrs.myDirective, function (tpl) {
destroyData();
if (tpl) {
tpl = '<div>' + tpl + '</div>';//make sure tpl is a html template.
$scope.$newScope = $scope.$new();
compiledTpl = $compile(tpl)($scope.$newScope);// compile the directive in string (trigger the directive render).
iEle.append(compiledTpl);
} else {
iEle.html('');
}
});
$scope.$on('$destroy', destroyData);//avoid memory leak.
}
};
}]);
HTML
<div my-directive="config[1].question"></div>
Link demo: https://jsfiddle.net/kw04qrdb/

Angularjs directive with bi-directional binding not working

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" />.

Resources