Angular Form HTML from API with ng-model not binding - angularjs

So I'm generating form HTML on the server and serving it up via an API for angular to use. The reason for this is that the forms need to be generated by server-side plugins. This may not be the best way do to it for Angular, but I'm asking whether it will be possible...
template.html
<form>
<div ng-bind-html="form"></div>
<button ng-click="save"></button>
</form>
directive.js (abridged)
ExtensionManagementService.getConfiguration({
extension_id: $scope.extension.id,
configuration_id: configuration.id || null
}).$promise.then(function(data) {
$scope.form = $sce.trustAsHtml(data['form']);
$scope.configuration = data.data;
})
The code above binds successfully into the div and I can see the form as I expect.
Example markup:
<p>
<label for="id_name">Name:</label>
<input id="id_name" name="name" ng-model="configuration.name" type="text" />
</p>
I have a save event that passes the scope.configuration into a controller which I then console out the values.
However configuration.name is always blank, I expect because angular hasn't registered the binding of the inserted markup.
Is there a way to essentially give Angular a nudge?

As #originof suggested, and a colleague almost beat him to it, the $compile module is the key to this:
ExtensionManagementService.getConfiguration({
extension_id: $scope.extension.id,
configuration_id: configuration.id || null
}).$promise.then(function(data) {
var form_element = $element.find('div[role="form"]');
form_element.html(data['form']);
$scope.configuration = data.data;
$compile(form_element.contents())($scope);
});

Related

angular scope initialization sometimes empty

I'm working with angular 1.5 and a python/mongodb api.
The api is working fine (consistent and fast), but sometimes when I load a page with a form the data fields are empty.
The app uses ui.router to associate the chap state with the correct url and the chapController. I type that url into the address bar and hit return to load the page. In the controller, the path/to/api returns json data.
Here's a simplified version of the controller:
angular.module('configurer')
.controller('chapController', function($scope, $http, $state) {
var url='path/to/api';
$http.get(url).success(function(data){
$scope.data = data;
});
$scope.save = function()( {
$http.post(url, $scope.data).success(function() {
$state.go('home', {reload:true});
});
});
The view looks like this:
<button class="btn btn-primary" ng-click="save()">Save Changes</button>
<form>
<div class="row">
<div class="col-sm-3 form-group">
<label for="name">Name</label>
<input class="form-control" id="name" type="text" name="name" ng-model="data.chap.name" />
</div>
<div class="col-sm-9 form-group">
<label for="title">Title</label>
<input class="form-control" id="title" type="text" title="title" ng-model="data.chap.title" />
</div>
</div>
</form>
Usually the form comes up populated with data but sometimes the fields are blank (using the same url).
I can reload the page from the browser and then it will populate, but of course that's not a good user experience.
What am I doing wrong?
After research plus trial-and-error, I think I've got the answer. The reason the scope wasn't binding to the data is that there was no data: my server was sending a 304 "not modified" response.
So of course it couldn't bind with non-existent data but the http response was still counted as success. Seems like this would be a common 'gotcha'.
What worked for me is to add a config object to each http.get call, like this
http.get(url, {cache:true}).success(function(data) { etc...
My guess is that you could also set max-age and public on the http request headers so the server cannot respond with a 304. That didn't seem like the most efficient thing to do, so I went with using cache on the client so the server isn't even bothered.
This is working for me, but if problems pop up again, I'll repost.
Since you're using ui.router, I recommend using resolve for this.
Example:
$stateProvider
.state('chap', {
// ...
resolve: {
data: function($http){
return $http.get(...);
}
}
});
and in your controller:
angular
.module('configurer')
.controller('chapController', function(data, $scope, $http, $state) {
$scope.data = data;
}
Sometimes data won't bind with the scope. can you check by including this line
$scope.data = data;
$scope.$apply();
Inside success handler.
I am not sure, it may work. please try

How to pass an array from one view to another within same controller

I am just a newbie to angularjs. I have a form in one view of my application which shows on my other view. There is only one controller for the application. The user can enter the fields in the form which should get displayed on the other view.
The code looks like this.
var app2 = angular.module('myApp2', ['ngRoute','ngStorage']);
app2.controller('rtCtrl', function($scope,$localStorage,$rootScope){
$scope.names = [
{name:'Jani',email:'jani#gmail.com'},
{name:'Hege',email:'hege#gmail.com'},
{name:'Kai',email:'kai#gmail.com'}
];
$rootScope.namesfinal = $scope.names;
$scope.saveData = function(){
$scope.names.push({name: $scope.username, email: $scope.emailaddress});
$localStorage.localData = $scope.names;
$rootScope.namesfinal = $localStorage.localData;
console.log($rootScope.namesfinal);
};
}
);
app2.config(['$routeProvider',function($routeProvider) {
$routeProvider.when('/page2', {
templateUrl: 'Home2.html',
controller: 'rtCtrl'
}).when('/page3', {
templateUrl: 'Home3.html',
controller: 'rtCtrl'
}).otherwise({
redirectTo: '/'
});
}]);
<div ng-app="myApp2" ng-controller="rtCtrl">
<!--a href="#page2">CLick here for page 2</a-->
<button ng-click="traverse()">Page2</button><br>
CLick here for page 3
<div ng-view></div>
</div>
//The second page comes here
<div>This is the second page<br>
<!--<button ng-click="locstor()">Click Here</button>-->
<span ng-repeat="x in namesfinal">
Name: <span ng-bind="x.name"></span> Email: <span ng-bind="x.email"></span><br>
</span>
</div>
//the form page comes here
<div>This is the third page
<form name="form1" novalidate>
Name: <input type="text" name="username" ng-model="username" required>
<span ng-show="form1.username.$pristine">Enter email here.</span>
<span style="color:red;" ng-show="form1.username.$dirty && form1.username.$invalid && form1.username.$error.required">
User name cannot be left empty.</span><br>
Email: <input type="email" name="emailaddress" ng-model="emailaddress" required>
<span style="color:red;" ng-show="form1.emailaddress.$error.email">Email is not valid.</span><br>
<!-- Password: <input type="password" ng-model="userpassword" required><span ng-show="">Password should be at least 8 characters long</span>-->
<input type="submit" value="Submit" ng-disabled="form1.username.$pristine || form1.emailaddress.$pristine || form1.username.$dirty && form1.username.$invalid || form1.emailaddress.$dirty && form1.emailaddress.$invalid"
ng-click="saveData()">
</form>
</div>
Can you tell me why is the array not getting updated on the view of the second page after clicking on the submit button.
Need to understand that although you are using the same controller ... each view will run a new instance of that controller and the scope from previous path (controller) is destroyed once view changes.
You need to use a service to share data across the application
You have two "instances" of your controller, one for each view. If you wan't to share data between those you can use services/factories. Angular guarantees that only one instance will exist of a given service during the app's lifecycle and this makes it an ideal place to share data.
Other approach is to use the rootScope. It looks easier but this solution can easily lead to a polluted rootScope. In general it is better to separate your code so it will be easier to write nice unit tests and maintain the code.
You use 1 controller for two separate pages. The problem is - when you go to another page your current controller will be destroyed and that next page's controller will be created. This will happen even if you use 1 controller function for 2 different pages. You can see it if you place debugger in your controller code. So you can't use controller to save state when moving to another page.
You can use factory for this. factories (and services and providers) are singletons in angular. So they won't be recreated when you go to another page.
tl;dr : create a factory to save your data when you move to another page

Form validation with modals in Angular

I have a form inside a modal pop up. I am trying to run form validation on the inputs after the user attempts to submit the form. So far, I'm struggling to make things work.
In my view, I have the following (sorry if there are any syntax errors, I'm converting this from jade on the fly):
<script type="text/ng-template", id="modalVideoNew">
<div class="ngdialog-message">
<form class="form-horizontal" ng-submit="submitForm()" novalidate name="newVideoForm">
...
<div class="form-group">
<label> Title </label>
<div class="col-sm-8">
<input type="text" name="title", required='', ng-model="newVideoForm.title">
<span class="text-danger" ng-show="validateInput('newVideoForm.title', 'required')"> This field is required</span>
</div>
</div>
</div>
</script>
And then in my controller, where I'm calling the ng-dialog pop up, I have this:
$scope.newVideo = function() {
ngDialog.openConfirm({
template: 'modalVideoNew',
className: 'ngdialog-theme-default',
scope: $scope
}).then(function() {
$scope.validateInput = function(name, type) {
var input = $scope.newVideoForm[name];
return (input.$dirty || $scope.submitted) && input.$error[type];
};
var newVideo = $scope.newVideoForm;
...
Right now, I am still able to submit the form, but once I open it back up I see the 'This field is required' error message. Also, the input is pre-filled with [object, Object] instead of an empty text input box.
A way of cleaning your model would work with using a model var that belongs to your parent controller and cleaning it in the callback. Check out how the template has attached your parent controller's var FormData.
Check this out
So about your validation, what I would recommend you is to have your own controller in it, no matter how much code it will have. It helps you keeping concepts of modularization and a better control over your scopes. This way will also facilitate a lot when validating.

How get form by name in $scope?

How to get form by name in $scope?
Test example:
<div ng-controller="solod">
<form name="good_f">
<input type="text" name="super">
</form>
</div>
<script>
function solod($scope){
console.log($scope.good_f) //undefined
}
</script>
Is it possible?
Thank you
You usually don't want the controller to access the form like this, that's coupling the controller to the structure of the view too tightly. Instead, pass the form to the controller like this...
<div ng-controller="solod">
<form name="good_f" ng-submit="submit(good_f)">
<input type="text" name="super">
</form>
</div>
<script>
function solod($scope){
$scope.submit = function(theForm){
console.log(theForm)// not undefined
console.log($scope.good_f) // will exist now also
};
// do stuff in a watch
$scope.$watch("good_f", function(formVal){ console.log(formVal);});
}
</script>
Otherwise, if you just want to track the value of the text input, give it an ng-model
Edit:
On further research, $scope will have good_f as a property, just not when you're logging it in the constructor. You could set up a watch on good_f if you wanted, but I still think you should pass it in.
name (optional) string Name of the form. If specified, the form
controller will be published into related scope, under this name.
https://docs.angularjs.org/api/ng/directive/form
Another possible way is to use ng-form, this will help you to access the form via scope easily.
<div ng-controller="solod">
<ng-form name="good_f">
<input type="text" name="super">
</ng-form>
</div>
Script code in your controller:
EDIT:
As JeremyWeir mentioned, to solve your problem you can use $timeout service of angularjs
function solod($scope){
$timeout(function(){
console.log($scope.good_f);
});
}
Caution: Don't use this - seriously
Angular is not jQuery.
As par as your question is concerned you can use $element in your controller(if you are not concrete with the $scope usage for this use case) -
myApp.controller("myCtrl", function($scope, $element){
alert($element.find('form').attr('name'));
$scope.myFormName = $element.find('form').attr('name');
});
PLNKR DEMO

Have AngularJS perform logic on form inputs when form submits

I currently have a form like the following:
<form autocomplete="on" enctype="multipart/form-data" accept-charset="UTF-8" method="POST" action="{{trustSrc(SUBMIT_URL)}}">
<input type="text" name="firstinput" ng-model="first"/>
<input type="text" name="secondinput" ng-model="second"/>
<input type='submit'/>
</form>
And a controller like so:
$scope.first = "first";
$scope.second = "second";
$scope.SUBMIT_URL = DATABASE_URL + "forms/submit/";
$scope.trustSrc = function(src) {
return $sce.trustAsResourceUrl(src);
};
And when I submit this form as is, it works just fine with the backend. However I now need to use ng-submit in place of the standard HTML form submit because I need to run a find and replace in $scope.first. The backend expects the data to be posted exactly in the way the original HTML form does. How do I use $http or $resource to post in the exact same format as the HTML form?
It is not very clear what you are trying to accomplish. But, you should tidy up your code a bit, to make the implementation easier:
Don't use different variables for each input's field. Instead, use an object with different keys.
Use ng-click (or ng-submit) for your submission. Action will go inside your JS logic.
Use the novalidate attribute so that Angular can properly format, and validate your form on its own (and you don't get confusing cross-browser effects).
With those in mind, your new markup would be:
<form autocomplete="on" enctype="multipart/form-data" accept-charset="UTF-8" novalidate>
<input type="text" name="first" ng-model="form.first" />
<input type="text" name="second" ng-model="form.second" />
<button ng-click="submit">Submit</button>
</form>
Your JS directive is then:
app.directive('form', function($http)
{
return function(scope, element, attrs)
{
// Leave this empty if you don't have an initial data set
scope.form = {
first : 'First Thing To Write',
second : 'Second item'
}
// Now submission is called
scope.submit = function()
{
// You have access to the variable scope.form
// This contains ALL of your form and is in the right format
// to be sent to an backend API call; it will be a JSON format
// Use $http or $resource to make your request now:
$http.post('/api/call', scope.form)
.success(function(response)
{
// Submission successful. Post-process the response
})
}
}
});

Resources