Related
I am trying to submit a form from controller function in AngularJS. I need to do this without using jQuery because the target site
gives me a cross domain error when I use jQuery. I need to just post the form. Is it possible to do from controller without using jQuery?
I tried to use $element service but it seems to be jquery way of doing it and it is not really going to the target url.
Thank you for any suggestions.
<form name="myForm" id="myForm" method="post" action="#Model.Settings["URL"]" ng-controller="FormCtrl as fctrl">
</form>
setControllers.controller('FormCtrl', ['$scope', '$state', '$element', 'DataService',
function ($scope, $state, $element, service) {
var fctrl = this;
function init() {
$element.find('#myForm').submit();
};
init();
}]);
Well, you can totally do
function init() {
document.getElementById("myForm").submit();
};
but neither my solution, nor your approach are very angular-ish. You should probably go for some sort of directive.
Well. The angular way of submitting a form is by using ng-submit. Here's an example of how you can achieve it, in its simplest form, pun intended:
html
<html ng-app="FormModule">
<section ng-controller="formController as form">
<form ng-submit="form.submit()">
<label>Name</label>
<input ng-model="form.data.name" placeholder="Insert your name" />
<button type="submit">Submit My Name To The Server</button>
</form>
</section>
</html>
javascript
angular
.module('FormModule', [])
.controller('formController', formCtrl);
formCtrl.$inject = ['$scope']; // or whatever injection
function formCtrl($scope) {
var form = this;
form.data = {}; // ng-model form.data.name will be available in this
form.submit = submitTheForm;
function submitTheForm() {
$.post('url', form.data); // or whatever HTTP service you're using
}
}
Anyway, to help you understand and write better Angular code, here's how I learned writing better, and it is reflected in the code above
John Papa Angular 1.x Styleguide
ngSubmit Documentation
I bind data of two different scopes to the same common service data. When I update the data through controller 1, the data in controller 2 is not refreshed in template.
Example showing the issue :
<body ng-app="demoShareBindApp">
<div ng-controller="FirstCtrl as first">
Set share data to : <a href ng-click="setShareDataTo('Me')">"Me"</a>
- <a href ng-click="setShareDataTo('Myself')">"Myself"</a>
- <a href ng-click="setShareDataTo('I')">"and I"</a>
</div>
<div ng-controller="SecondCtrl as second">
Text entered : {{sShareData}}
<br>
<br><a href ng-click="revealShareData()">Reveal data</a>
</div>
<script src="bower_components/angular/angular.js"></script>
<script>
angular
.module('demoShareBindApp', [])
.service('myService', function () {
return {
shareData: null
}
})
.controller('FirstCtrl', ['$scope', 'myService', function ($scope, myService) {
$scope.setShareDataTo = function(content) {
myService.shareData = content;
};
}])
.controller('SecondCtrl', ['$scope', 'myService', function ($scope, myService) {
$scope.sShareData = myService.shareData;
$scope.revealShareData = function() {
console.log(myService.shareData);
}
}]);
</script>
</body>
The Text entered : {{sShareData}} is never updated whereas clicking "Reveal data" shows the right share data in console.
I can't find any clue in other SO post on this particular subject. I guess it could be a matter of "$watch/$digest" but I can't figure out what is really going on here.
Any detailed explanation welcome !
You are sharing data through service. When data in changed under first controller, you set this updated data to service but second controller does not know that shared data referenced by service has changed, so we need to notify the second controller that data has changed or we can shared data through events
I created a fiddle, check it
https://jsbin.com/midebu/edit?html,js,output
First approach. Using service
$scope.setShareDataTo = function(content) {
myService.shareData = content;
$rootScope.$broadcast('datacChanged');
};
In second cntroller
$rootScope.$on('dataChanged', function(){
// get updated data
$scope.sShareData = myService.shareData;
})
Other way is that, we do not need to use service , we can simply pass that shared data using events
$scope.setShareDataTo = function(content) {
$rootScope.$broadcast('datacChanged', content);
};
And in Second controller
$rootScope.$on('dataChanged', function(event, data){
// get updated data
$scope.shareData = data;
})
I have a code snippet in my content which is a model fetched from http. I am using syntax highlighter to prettify the code. So I need to call a javascript function as soon as the DOM is updated for that particular model.
Here is a sample code to make it clear. I am using alert to demonstrate it. In my project I would use a third party plugin which will find matching dom elements and remodel them.
Here,
I want the alert to occur after the list is displayed
jsfiddle :
http://jsfiddle.net/7xZde/2/
My controller has something like this.
$scope.items = Model.notes();
alert('test');
alert comes even before the items list is shown, I want it after the list is displayed.
Any hint to help me achieve this.
We need to use $timeout ,
$scope.items = Model.notes();
$timeout(function () {
alert('test');
})
Yeah it was silly , $timeout seemed to be a misnomer to me. I am 2 days old to angularjs . Sorry for wasting your time.
Lucky for you, I wanted to do the exact same thing. Mutation observers are the path forward, but if you need backwards compatibility with older browsers, you'll need a bit more code than this.
Working plunker for Firefox, Chrome, and Safari.
Javascript:
var app = angular.module('plunker', [])
.controller('MainCtrl', function($scope) {
$scope.name = 'World';
})
.directive('watchChanges', function ($parse, $timeout) {
return function (scope, element, attrs) {
var setter = $parse(attrs.watchChanges).assign;
// create an observer instance
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$timeout(function () {
var text = angular.element('<div></div>').text(element.html()).text();
setter(scope, text);
});
});
});
// configuration of the observer:
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true
};
// pass in the target node, as well as the observer options
observer.observe(element[0], config);
};
});
HTML:
<body ng-controller="MainCtrl">
<div watch-changes="text">
<p>Hello {{ name }}</p>
<label>Name</label>
<input type="text" ng-model="name" />
</div>
<pre>{{text}}</pre>
</body>
Say you have a form that has values loaded from database. How do you initialize ng-model?
Example:
<input name="card[description]" ng-model="card.description" value="Visa-4242">
In my controller, $scope.card is undefined initially. Is there a way besides doing something like this?
$scope.card = {
description: $('myinput').val()
}
If you can't rework your app to do what #blesh suggests (pull JSON data down with $http or $resource and populate $scope), you can use ng-init instead:
<input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">
See also AngularJS - Value attribute on an input text box is ignored when there is a ng-model used?
This is a common mistake in new Angular applications. You don't want to write your values into your HTML on the server if you can avoid it. If fact, if you can get away from having your server render HTML entirely, all the better.
Ideally, you want to send out your Angular HTML templates, then pull down your values via $http in JSON and put them in your scope.
So if at all possible, do this:
app.controller('MyController', function($scope, $http) {
$http.get('/getCardInfo.php', function(data) {
$scope.card = data;
});
});
<input type="text" ng-model="card.description" />
If you absolutely MUST render your values into your HTML from your server, you could put them in a global variable and access them with $window:
In the header of your page you'd write out:
<head>
<script>
window.card = { description: 'foo' };
</script>
</head>
And then in your controller you'd get it like so:
app.controller('MyController', function($scope, $window) {
$scope.card = $window.card;
});
This is an obviously lacking, but easily added fix for AngularJS. Just write a quick directive to set the model value from the input field.
<input name="card[description]" value="Visa-4242" ng-model="card.description" ng-initial>
Here's my version:
var app = angular.module('forms', []);
app.directive('ngInitial', function() {
return {
restrict: 'A',
controller: [
'$scope', '$element', '$attrs', '$parse', function($scope, $element, $attrs, $parse) {
var getter, setter, val;
val = $attrs.ngInitial || $attrs.value;
getter = $parse($attrs.ngModel);
setter = getter.assign;
setter($scope, val);
}
]
};
});
IMHO the best solution is the #Kevin Stone directive, but I had to upgrade it to work in every conditions (f.e. select, textarea), and this one is working for sure:
angular.module('app').directive('ngInitial', function($parse) {
return {
restrict: "A",
compile: function($element, $attrs) {
var initialValue = $attrs.value || $element.val();
return {
pre: function($scope, $element, $attrs) {
$parse($attrs.ngModel).assign($scope, initialValue);
}
}
}
}
});
You can use a custom directive (with support to textarea, select, radio and checkbox), check out this blog post https://glaucocustodio.github.io/2014/10/20/init-ng-model-from-form-fields-attributes/.
You can also use within your HTML code:
ng-init="card.description = 12345"
It is not recommended by Angular, and as mentioned above you should use exclusively your controller.
But it works :)
I have a simple approach, because i have some heavy validations and masks in my forms. So, i used jquery to get my value again and fire the event "change" to validations:
$('#myidelement').val('123');
$('#myidelement').trigger( "change");
As others pointed out, it is not good practice to initialize data on views.
Initializing data on Controllers, however, is recommended. (see http://docs.angularjs.org/guide/controller)
So you can write
<input name="card[description]" ng-model="card.description">
and
$scope.card = { description: 'Visa-4242' };
$http.get('/getCardInfo.php', function(data) {
$scope.card = data;
});
This way the views do not contain data, and the controller initializes the value while the real values are being loaded.
If you like Kevin Stone's approach above https://stackoverflow.com/a/17823590/584761
consider an easier approach by writing directives for specific tags such as 'input'.
app.directive('input', function ($parse) {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs) {
if (attrs.ngModel) {
val = attrs.value || element.text();
$parse(attrs.ngModel).assign(scope, val);
}
}
}; });
If you go this route you won't have to worry about adding ng-initial to every tag. It automatically sets the value of the model to the tag's value attribute. If you do not set the value attribute it will default to an empty string.
Here is a server-centric approach:
<html ng-app="project">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
// Create your module
var dependencies = [];
var app = angular.module('project', dependencies);
// Create a 'defaults' service
app.value("defaults", /* your server-side JSON here */);
// Create a controller that uses the service
app.controller('PageController', function(defaults, $scope) {
// Populate your model with the service
$scope.card = defaults;
});
</script>
<body>
<div ng-controller="PageController">
<!-- Bind with the standard ng-model approach -->
<input type="text" ng-model="card.description">
</div>
</body>
</html>
It's the same basic idea as the more popular answers on this question, except $provide.value registers a service that contains your default values.
So, on the server, you could have something like:
{
description: "Visa-4242"
}
And put it into your page via the server-side tech of your choice. Here's a Gist: https://gist.github.com/exclsr/c8c391d16319b2d31a43
This one is a more generic version of the ideas mentioned above...
It simply checks whether there is any value in the model, and if not, it sets the value to the model.
JS:
function defaultValueDirective() {
return {
restrict: 'A',
controller: [
'$scope', '$attrs', '$parse',
function ($scope, $attrs, $parse) {
var getter = $parse($attrs.ngModel);
var setter = getter.assign;
var value = getter();
if (value === undefined || value === null) {
var defaultValueGetter = $parse($attrs.defaultValue);
setter($scope, defaultValueGetter());
}
}
]
}
}
HTML (usage example):
<select class="form-control"
ng-options="v for (i, v) in compressionMethods"
ng-model="action.parameters.Method"
default-value="'LZMA2'"></select>
I tried what #Mark Rajcok suggested. Its working for String values (Visa-4242).
Please refer this fiddle.
From the fiddle:
The same thing that is done in the fiddle can be done using ng-repeat, which everybody could recommend. But after reading the answer given by #Mark Rajcok, i just wanted to try the same for a form with array of profiles.
Things work well untill i have the $scope.profiles = [{},{}]; code in the controller. If i remove this code, im getting errors.
But in normal scenarios i cant print $scope.profiles = [{},{}]; as i print or echo html from the server.
Will it be possible to execute the above, in a similar fashion as #Mark Rajcok did for the string values like <input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">, without having to echo the JavaScript part from the server.
Just added support for select element to Ryan Montgomery "fix"
<select class="input-control" ng-model="regCompModel.numberOfEmployeeId" ng-initial>
<option value="1af38656-a752-4a98-a827-004a0767a52d"> More than 500</option>
<option value="233a2783-db42-4fdb-b191-0f97d2d9fd43"> Between 250 and 500</option>
<option value="2bab0669-550c-4555-ae9f-1fdafdb872e5"> Between 100 and 250</option>
<option value="d471e43b-196c-46e0-9b32-21e24e5469b4"> Between 50 and 100</option>
<option value="ccdad63f-69be-449f-8b2c-25f844dd19c1"> Between 20 and 50</option>
<option value="e00637a2-e3e8-4883-9e11-94e58af6e4b7" selected> Less then 20</option>
</select>
app.directive('ngInitial', function () {
return {
restrict: 'A',
controller: ['$scope', '$element', '$attrs', '$parse', function ($scope, $element, $attrs, $parse) {
val = $attrs.sbInitial || $attrs.value || $element.val() || $element.text()
getter = $parse($attrs.ngModel)
setter = getter.assign
setter($scope, val)
}]
}
});
If you have the init value in the URL like mypage/id, then in the controller of the angular JS you can use location.pathname to find the id and assign it to the model you want.
Say you have a form that has values loaded from database. How do you initialize ng-model?
Example:
<input name="card[description]" ng-model="card.description" value="Visa-4242">
In my controller, $scope.card is undefined initially. Is there a way besides doing something like this?
$scope.card = {
description: $('myinput').val()
}
If you can't rework your app to do what #blesh suggests (pull JSON data down with $http or $resource and populate $scope), you can use ng-init instead:
<input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">
See also AngularJS - Value attribute on an input text box is ignored when there is a ng-model used?
This is a common mistake in new Angular applications. You don't want to write your values into your HTML on the server if you can avoid it. If fact, if you can get away from having your server render HTML entirely, all the better.
Ideally, you want to send out your Angular HTML templates, then pull down your values via $http in JSON and put them in your scope.
So if at all possible, do this:
app.controller('MyController', function($scope, $http) {
$http.get('/getCardInfo.php', function(data) {
$scope.card = data;
});
});
<input type="text" ng-model="card.description" />
If you absolutely MUST render your values into your HTML from your server, you could put them in a global variable and access them with $window:
In the header of your page you'd write out:
<head>
<script>
window.card = { description: 'foo' };
</script>
</head>
And then in your controller you'd get it like so:
app.controller('MyController', function($scope, $window) {
$scope.card = $window.card;
});
This is an obviously lacking, but easily added fix for AngularJS. Just write a quick directive to set the model value from the input field.
<input name="card[description]" value="Visa-4242" ng-model="card.description" ng-initial>
Here's my version:
var app = angular.module('forms', []);
app.directive('ngInitial', function() {
return {
restrict: 'A',
controller: [
'$scope', '$element', '$attrs', '$parse', function($scope, $element, $attrs, $parse) {
var getter, setter, val;
val = $attrs.ngInitial || $attrs.value;
getter = $parse($attrs.ngModel);
setter = getter.assign;
setter($scope, val);
}
]
};
});
IMHO the best solution is the #Kevin Stone directive, but I had to upgrade it to work in every conditions (f.e. select, textarea), and this one is working for sure:
angular.module('app').directive('ngInitial', function($parse) {
return {
restrict: "A",
compile: function($element, $attrs) {
var initialValue = $attrs.value || $element.val();
return {
pre: function($scope, $element, $attrs) {
$parse($attrs.ngModel).assign($scope, initialValue);
}
}
}
}
});
You can use a custom directive (with support to textarea, select, radio and checkbox), check out this blog post https://glaucocustodio.github.io/2014/10/20/init-ng-model-from-form-fields-attributes/.
You can also use within your HTML code:
ng-init="card.description = 12345"
It is not recommended by Angular, and as mentioned above you should use exclusively your controller.
But it works :)
I have a simple approach, because i have some heavy validations and masks in my forms. So, i used jquery to get my value again and fire the event "change" to validations:
$('#myidelement').val('123');
$('#myidelement').trigger( "change");
As others pointed out, it is not good practice to initialize data on views.
Initializing data on Controllers, however, is recommended. (see http://docs.angularjs.org/guide/controller)
So you can write
<input name="card[description]" ng-model="card.description">
and
$scope.card = { description: 'Visa-4242' };
$http.get('/getCardInfo.php', function(data) {
$scope.card = data;
});
This way the views do not contain data, and the controller initializes the value while the real values are being loaded.
If you like Kevin Stone's approach above https://stackoverflow.com/a/17823590/584761
consider an easier approach by writing directives for specific tags such as 'input'.
app.directive('input', function ($parse) {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs) {
if (attrs.ngModel) {
val = attrs.value || element.text();
$parse(attrs.ngModel).assign(scope, val);
}
}
}; });
If you go this route you won't have to worry about adding ng-initial to every tag. It automatically sets the value of the model to the tag's value attribute. If you do not set the value attribute it will default to an empty string.
Here is a server-centric approach:
<html ng-app="project">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
// Create your module
var dependencies = [];
var app = angular.module('project', dependencies);
// Create a 'defaults' service
app.value("defaults", /* your server-side JSON here */);
// Create a controller that uses the service
app.controller('PageController', function(defaults, $scope) {
// Populate your model with the service
$scope.card = defaults;
});
</script>
<body>
<div ng-controller="PageController">
<!-- Bind with the standard ng-model approach -->
<input type="text" ng-model="card.description">
</div>
</body>
</html>
It's the same basic idea as the more popular answers on this question, except $provide.value registers a service that contains your default values.
So, on the server, you could have something like:
{
description: "Visa-4242"
}
And put it into your page via the server-side tech of your choice. Here's a Gist: https://gist.github.com/exclsr/c8c391d16319b2d31a43
This one is a more generic version of the ideas mentioned above...
It simply checks whether there is any value in the model, and if not, it sets the value to the model.
JS:
function defaultValueDirective() {
return {
restrict: 'A',
controller: [
'$scope', '$attrs', '$parse',
function ($scope, $attrs, $parse) {
var getter = $parse($attrs.ngModel);
var setter = getter.assign;
var value = getter();
if (value === undefined || value === null) {
var defaultValueGetter = $parse($attrs.defaultValue);
setter($scope, defaultValueGetter());
}
}
]
}
}
HTML (usage example):
<select class="form-control"
ng-options="v for (i, v) in compressionMethods"
ng-model="action.parameters.Method"
default-value="'LZMA2'"></select>
I tried what #Mark Rajcok suggested. Its working for String values (Visa-4242).
Please refer this fiddle.
From the fiddle:
The same thing that is done in the fiddle can be done using ng-repeat, which everybody could recommend. But after reading the answer given by #Mark Rajcok, i just wanted to try the same for a form with array of profiles.
Things work well untill i have the $scope.profiles = [{},{}]; code in the controller. If i remove this code, im getting errors.
But in normal scenarios i cant print $scope.profiles = [{},{}]; as i print or echo html from the server.
Will it be possible to execute the above, in a similar fashion as #Mark Rajcok did for the string values like <input name="card[description]" ng-model="card.description" ng-init="card.description='Visa-4242'">, without having to echo the JavaScript part from the server.
Just added support for select element to Ryan Montgomery "fix"
<select class="input-control" ng-model="regCompModel.numberOfEmployeeId" ng-initial>
<option value="1af38656-a752-4a98-a827-004a0767a52d"> More than 500</option>
<option value="233a2783-db42-4fdb-b191-0f97d2d9fd43"> Between 250 and 500</option>
<option value="2bab0669-550c-4555-ae9f-1fdafdb872e5"> Between 100 and 250</option>
<option value="d471e43b-196c-46e0-9b32-21e24e5469b4"> Between 50 and 100</option>
<option value="ccdad63f-69be-449f-8b2c-25f844dd19c1"> Between 20 and 50</option>
<option value="e00637a2-e3e8-4883-9e11-94e58af6e4b7" selected> Less then 20</option>
</select>
app.directive('ngInitial', function () {
return {
restrict: 'A',
controller: ['$scope', '$element', '$attrs', '$parse', function ($scope, $element, $attrs, $parse) {
val = $attrs.sbInitial || $attrs.value || $element.val() || $element.text()
getter = $parse($attrs.ngModel)
setter = getter.assign
setter($scope, val)
}]
}
});
If you have the init value in the URL like mypage/id, then in the controller of the angular JS you can use location.pathname to find the id and assign it to the model you want.