This is my first project on angular. I have created a directive. I am not able to insert the value which I am fetching from an object served by a RESTful API. The object as shown in the console is like this,
Below are the necessary files,
knob.js(directive)
angular.module('knob', [])
.directive('knob', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.knob({
fgColor: attrs.fgColor
});
}
}
})
dashboard.js(controller)
myApp.controller('DashController', function($scope, $http, $cookies, $cookieStore) {
$http({
url: site + '/company/dashboard',
method: "GET",
transformRequest: encodeurl,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).success(function(data) {
console.log(data); //data has the object that i want to use
$scope.dash = data.count;
}).error(function(res) {
console.log(res);
});
});
dahboard.html(view)
<li>
<div class="knob_contain">
<h4 class="knobs_title">Deals Left This Month</h4>
<input type="text" value="{{dash.profile}}" knob class="dial dialIndex">
</div>
</li>
I want the count.profile value to be bound to input type where I am using my knob. I will give you more files if you need.
The better way is assign a model to text box like,
<input type="text" ng-model="dash.profile" knob class="dial dialIndex">
You can change your code look like
AngulaJs code
var app = angular.module('app', []);
app.service('yourService', function ($http) {
this.getUser = function () {
return $http({ url: site + '/company/dashboard',
method: "GET",
transformRequest: encodeurl,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.success(function(data) {
console.log(data); //data has the object that i want to use
$scope.dash = data;
}).error(function(res) {
console.log(res);
});
};
});
app.controller('DashController', function($scope, $http, $cookies, $cookieStore, yourService) {
$scope.dash =null;
yourService.getUser().then(function (resp) {
if (resp.data !== undefined) {
console.log(data); //data has the object that i want to use
$scope.dash = data.count;
}
})
.error(function(res){
console.log(res);
});
});
app.directive('knob', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.knob({
fgColor: attrs.fgColor
});
}
}
});
HTML code sample
< li>
<div class="knob_contain">
<h4 class="knobs_title">Deals Left This Month</h4>
<input type="text" value="{{dash.profile}}" knob class="dial dialIndex">
</div>
</li>
You need to make a small change in your dashboard.html page.
For input elements, the ng-model property sets the value of the input box. For details see the docs on input[text]
So instead of using
<input type="text" value="{{dash.profile}}" knob class="dial dialIndex">
use
<input type="text" ng-model="dash.profile" knob class="dial dialIndex">
Related
EDIT->I am also using a ui-router- Can i use resolve
name: 'PLANSPONSOR.SUMMARY',
state: {
url: "/summary",
templateUrl: '../script/planSummary.html',
controller: "summaryCtrl",params:{obj:null}
}
}
]
I am trying to trigger the API before the directive in my controller.
The Directive needs my API to be called to get the data so that it can populate on the page.
When i load the page the directive fires because its called in HTML and the API is triggered next.
Can anybody help me on Using the $watch function or do i need to use something else so that API is triggered on the page and then Directive.
API CODE (trimmed for code sanity)
$timeout(function () {$http({
method: 'GET',
url: 'getPSDetails?psuid='+$scope.psuId,
//url: 'getPSDetails?controlNumber='+$scope.DataEntered,
}).success(function (data) {
console.log('success response'); }
$scope.summaryObject =data; ( I am getting all the data here )
My Directive. (trimmed for code sanity)
myapp.directive('summaryFeatureDetails',function(){
return{
restrict: 'E',
scope:true,
controller:function($scope){
$scope.columnPerPage = 5;
$scope.currentPage = 0;
$scope.currentColumns = [];
$scope.controlData = [];
$scope.totalNumberOfPage = Math.floor($scope.selectedData.length/$scope.columnPerPage);
if (($scope.selectedData.length % $scope.columnPerPage) > 0){
$scope.totalNumberOfPage = $scope.totalNumberOfPage + 1;
}
}
}
If the directive loads before the $scope.summaryObject is set then make sure to load the directive after the object is set.
This can be done by simply adding an NgIf expression on the directive tag which checks the object value and only render the html if the object is not null
<yourDirectiveTag ng-if="!summaryObject" ></yourDirectiveTag>
I will try an answer to help you with what I see. This is how I do my stuff:
Controller:
var vm = this;
vm.dataToDisplay = [];
...
$http({
method: 'GET',
url: 'getPSDetails?psuid='+$scope.psuId,
}).success(function (data) {
Array.prototype.push.apply(vm.dataToDisplay, data);
}
Directive:
myapp.directive('summaryFeatureDetails',function(){
return{
restrict: 'E',
scope: {
myData: '='
}
/* No controller */
HTML:
<summary-feature-details my-data="vm.dataToDisplay"></summary-feature-details>
I want to call a validateFormat() function each time an input of type 'file' changes. Note that a custom directive is used 'file-model'
If I try to add 'ng-change="validateFormat()"' to the input, the console prints the following error: no controller: ngModel.
How can I bind this element to ng-change?
Html
<div ng-controller="MyControllerController">
<input type="file" file-model="file" />
<button ng-click="uploadFile()">Upload</button>
{{error}}
</div>
Angular 1.x:
var module = angular.module("umbraco");
module.controller('OnlineServices.JobCentreUploadController', [
'$scope', 'fileUpload', function($scope,
$scope.uploadFile = function () {
var file = $scope.file;
var uploadUrl = "/umbraco/api/jc/Upload";
fileUpload.uploadFileToUrl(file, uploadUrl, $scope);
};
}]);
module.directive('fileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function () {
scope.$apply(function () {
modelSetter(scope, element[0].files[0]);
});
});
}
};
}]);
module.service('fileUpload', ['$http', 'notificationsService', function ($http, notificationsService) {
this.uploadFileToUrl = function (file, uploadUrl) {
var fd = new FormData();
fd.append('file', file);
$http.post(uploadUrl, fd, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
})
.success(function () {
notificationsService.success("Success", "File uploaded successfully.");
})
.error(function () {
notificationsService.error("Error", "File has not been uploaded.");
});
}
}]);
ng-change directive needs ng-model
Behind the screen ng-change sets a watch for the corresponding ng-model.
Coming to your problem you are trying to call ng-change without specifying ng-model on it that is the reason for error.
In your custom directive you've created an event handler for change event.
So if you want to set up ng-change event for your input element you can do it in your custom directive only no need to go for ng-change directive.
In order to do so you need to pass your validateFormat function as paramter to custom directive and execute the function in change callback like below:
Example code:
element.bind('change', function () {
scope.$apply(function () {
modelSetter(scope, element[0].files[0]);
});
//Get this function using isolated scope and call it
validateFormat();
});
I created directives for form controls.
There are some controls those will have options from ajax[API] call.
I am stuck here how to make ajax call over directives.
function selectControlDir()
{
return {
transclude: true,
restrict: 'E',
scope: {
data: '=data'
},
template: "<div ng-transclude></div><label>{{data._text}} </label><select type='text' name='{{data._attributeName}}' id='{{data._attributeName}}' >\n\
<option ng-repeat='ans in data._answerOptions'>{{ans._promptText}}</option></select>"
,
link: function (scope, element, attrs)
{
//console.log("scope.data.QuestionData", scope.data.QuestionData);
}
};
}
Plunker for complete code
http://plnkr.co/edit/Op1QDwUBECAosPUC7r3N?p=preview
Here I want to make api call for Year (on page load).
on year change update options of Make.
What you can do for this is better to have a ng-change event directive on the year element and in the controller you can use to have an array which holds all the make happened in that year:
var app = angular.module('yourApp', []);
app.controller('yourController', ['$scope', '$http',
function($scope, $http) {
$scope.o = {
makes: [{make:'Make1'}, {make:'Make2'}], // <---just for demo
getMake: function(year) {
$http.post(url, {
year: year // <----pass the year to backend as year like : { year:1990 }
})
.then(function success(response) {
$scope.o.makes = response.data; // <---put actual data here
},
function error(e) {
console.error(e);
});
}
};
}
]);
<div ng-app='yourApp' ng-controller='yourController'>
<select ng-model='year' ng-change='o.getMake(year)'>
<option>1990</option>
<option>1991</option>
</select>
<select ng-model='make' ng-options='make as make.make for make in o.makes track by make.make'>
<option>Make...</option>
</select>
</div>
You should get the options by angular service. The angular service will send the AJAX call and return the result (and even some default data in case of failure).
How to do it? Here is an example:
var app = angular.module('app',[]);
//Service
app.factory('myService', function(){
function getOption() {
return 'getOption';
}
return {
getOption: getOption
}
});
app.directive('myDirective', ['myService',function(myService){
return {
template: '<h1>{{name}}</h1>'
link: function (scope, element, attrs) {
scope.name = myService.getOption();
}
}
}]);
Use can use $http to make ajax call. You need to inject it to use it. Better solution is to move the service calls to factory/service.
function selectControlDir($http)
{
return {
transclude: true,
restrict: 'E',
scope: {
data: '=data'
},
template: "<div ng-transclude></div><label>{{data._text}} </label
> ><select type='text' name='{{data._attributeName}}' id='{{data._attributeName}}' >\n\ <option ng-repeat='ans in
> data._answerOptions'>{{ans._promptText}}</option></select>"
,
link: function (scope, element, attrs)
{
// $http.post(url, options); => make your call here
//console.log("scope.data.QuestionData", scope.data.QuestionData);
}
};
}
The best way is to create a service or a factory and inject it into your directive.
app.service("serviceName", ["$http", function($http) {
return {
getData: function(configObject) {
// $http.get(..., post... etc
}
}
}]);
You can use it in your link or in controller statement.
selectControlDir(serviceName) {
return{
...
link: function() {
selectControlDir.getData({ url: ..., getParam1: 123})
}
... or ...
controller: function() {
selectControlDir.getData({ url: ..., getParam1: 123})
}
}
}
I have the following code.
controller.js
angular.module('LiveAPP.main',['LiveAPP.factory'])
.controller('mainCtrl', ['$scope','$http', '$location','dataFactory',mainCtrl])
.directive('ratehome',function(){
return {
restrict:"E",
template: "<div id='rateYo'></div>",
link: function(scope, ele, attrs){
console.log("NEW",scope.recentArtist)
}
}
})
function mainCtrl($scope,$http,$location,dataFactory){
$scope.getRecentArtists = function(){
return $http({
method: 'GET',
url: '/artistsearch',
params: {getArtist: "all"}
}).then(function(recent){
$scope.recentArtist = recent.data
})
};
$scope.getRecentArtists();
$scope.recentArtist = ""
$scope.$watch('recentArtist',function(newValue,oldValue){
$scope.recentArtist = newValue
})
}
test.html
<ratehome></ratehome>
<ratehome></ratehome>
<ratehome></ratehome>
What happens here is upon instantiation of my controller(routing is set up correctly) there is a $http GET request that responds with data that I need that gets assigned to $scope.recentArtist. I want this data to be accessible in my link function, but it's not. I have a feeling that my directive is compiling before this request is sent. Is there any way around this? What is odd to me is that when I console.log(scope) and check in Chrome Developer Tools my data is there. Yet when I console.log(scope.recentArtist) its empty similar to its state in the controller. I was thinking I could maybe make the $http.get in my directive, but that seems a little awkward to me.
I have been having trouble with this problem for a few days, hopefully somebody can help me out.
If you're using angular ui-router you could also use resolve. With resolve you can do the $http before the controller starts.
You can use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.
If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the
$stateChangeSuccess event is fired.
from the docs.
Please have a look at the demo below or this jsfiddle.
angular.module('demoApp', ['ui.router'])
.config(function ($urlRouterProvider, $stateProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider.state('home', {
url: '/',
template: '<ratehome></ratehome><ratehome></ratehome><ratehome></ratehome>',
controller: 'mainCtrl',
resolve: {
artists: function (artistsService) {
console.log('Resolve');
return artistsService.get(); //'/artistsearch',//artistsService.get();
}
}
});
})
.controller('mainCtrl', ['$scope', '$http', '$location', 'artists', MainCtrl])
.directive('ratehome', function () {
return {
restrict: "E",
template: '<div id="rateYo"><ul><li ng-repeat="artist in recentArtist">{{artist}}</li></ul></div>',
link: function (scope, elem, attrs) {
console.log("NEW", scope.recentArtist);
}
}
})
.factory('artistsService', function ($http) {
return {
get: function () {
console.log('getting');
return $http({
method: 'GET',
url: 'http://crossorigin.me/http://www.mocky.io/v2/558b30615f3dcbc414067170', //'/artistsearch',
//params: {getArtist: "all"}
}).then(function (recent) {
//console.log(recent);
return recent.data;
});
}
};
});
function MainCtrl($scope, $http, $location, artists) {
$scope.recentArtist = artists;
console.log('ctrl', artists);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.js"></script>
<div ng-app="demoApp">
<div ui-view=""></div>
</div>
AS your directive is not using isolated scope, that does mean you can directly access your controller scope inside your directive. I'd suggest you to to put that $watch inside directive link instead of your controller. That would intimate you that the ajax has been completed and data got changed & you get those changed value inside watcher function.
Code
.directive('ratehome',function(){
return {
restrict:"E",
template: "<div id='rateYo'></div>",
link: function(scope, ele, attrs){
$scope.$watch('recentArtist',function(newValue,oldValue){
console.log("NEW",newValue)
});
}
}
})
Your link function is running before the $http response comes back as you suspect. You can wait for it by using $broadcast and $on:
angular.module('LiveAPP.main',['LiveAPP.factory'])
.controller('mainCtrl', ['$scope','$http', '$location','$rootScope','dataFactory',mainCtrl])
.directive('ratehome',function(){
return {
restrict:"E",
template: "<div id='rateYo'></div>",
link: function(scope, ele, attrs){
scope.$on('artistLoaded', function(){
console.log("NEW",scope.recentArtist);
});
}
};
});
function mainCtrl($scope,$http,$location,$rootScope,dataFactory){
$scope.getRecentArtists = function(){
return $http({
method: 'GET',
url: '/artistsearch',
params: {getArtist: "all"}
}).then(function(recent){
$scope.recentArtist = recent.data
$rootScope.$broadcast('artistLoaded');
});
};
$scope.getRecentArtists();
$scope.recentArtist = "";
$scope.$watch('recentArtist',function(newValue,oldValue){
$scope.recentArtist = newValue
});
}
This way the code will not run until the response has been returned and set
Suppose I have a directive in which a template contains some data that provide another JS to draw a piechart:
mymodule.directive('myGauge', ['$http', function() {
return {
restrict: 'E',
template: '<div class="quota-dynamic"><h3>Limit Summary</h3><div class="d3_quota_bar"><div class="pie_chart" data-used="{$ dataUsed $}"></div></div>'
}
}]);
whereas "pie_chart" is used in another JS and it relies data-used value to draw the piechart.
I have another controller which receive the data:
mymodule.controller('MyCtrl', ['$scope', '$http', function($scope, $http) {
$scope.$parent.$watch("toggled", function(toggled) {
var tenant_id = 1234;
if (!$scope.$parent.isCollapsed()) {
$http({
method: 'get',
url: 'http://localhost:8080/admin/projects/' + tenant_id + '/test/'
}).success(function (data, status) {
$scope.dataUsed = data.totalInstancesUsed / data.maxTotalInstances * 100;
}).error(function () {
});
}
});
}]);
When I receive the data, it is too late for the piechart as it is drawn already. I would like to know if there is a way to force the directive to update the template with new data again?
Thanks.
If you need to recompile the template because there are others directive that have to be rerendered, you can add this inside your directive:
controller: function($scope,$element){
$scope.$watch( 'dataUsed', function( newVal, oldVal ){
$compile($element.contents())($scope);
});
}
This is valid if the scope of the directive is the same as MyCtrl
You will need to keep watch in the controller of your directive.
$scope.$watch('dataUsed', function(oldValue, newValue) {
console.log(oldValue, newValue)
}, true)