angular scope initialization sometimes empty - angularjs

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

Related

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

Why is my AngularJS module never loaded?

I am an AngularJS newbie, and am having difficulty getting an AngularJS form to work correctly. Chrome Dev Tools tells me my module is not being loaded.
Below is my html; it is a div within a Wordpress template:
<div ng-app="contact" style="float: left;">
<form method="post" name="form" role="form" ng-controller="ContactFormController" ng-submit="form.$valid && sendMessage(input)" novalidate>
<p ng-show="success">Thanks for getting in touch!</p>
<p ng-show="error">Something wrong happened!, please try again.</p>
<legend>Contact</legend>
<fieldset>
<div>
<label for="name">Name:</label>
<input type="text" id="name" name="name" ng-model="input.name" required>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" ng-model="input.email" required>
</div>
<div>
<label for="messsage">Message:</label>
<textarea id="messsage" name="message" ng-model="input.message" required></textarea>
</div>
<div>
<label for="honeypot">I promise I'm not a bot</label>
<input type="text" id="honeypot" name="honeypot" ng-model="input.honeyPot">
</div>
</fieldset>
<button type="submit" name="submit" value="submit">Submit</button>
</form>
</div>
This is my angular code:
angular.module("contact", [])
.controller("ContactFormController", ['$scope', '$http', function($scope, $http) {
$scope.success = false;
$scope.error = false;
$scope.sendMessage = function( input ) {
$http({
method: 'POST',
url: 'http://alamanceforeducation.org/wp-content/themes/flex/library/scripts/processApplecartForm.php',
data: input,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
.success( function(data) {
if ( data.success ) {
$scope.success = true;
} else {
$scope.error = true;
}
} );
}
}]);
The code is adapted from a tutorial. Can anyone help me out with what I am doing wrong?
There's nothing wrong with your code in terms of the problem you are having. The problem is the order in which you are loading the scripts.
The order should be:
1- angular.js
2- any-angular-dependency.js
3- your-angular-app.js
this is a working copy of your code
I'm not including the form because is just to show you that it doesn't find your angular module because either you are not including it or is not loading correctly from cdn, you might want to download it to have it locally and include the <script src="angular.js"></script> tag
In order to add a new module you need to make sure that:
The JS file is properly loaded on your page together with the other modules and angular.js
The module is properly declared for example: angular.module('myModule').directive(....) is not a module declaration, because doesn't have the dependencies array like this: angular.module('myModule',[]).directive(....)
Add the dependency on the declaration of your app, or add the module as dependency of other which already is a dependency of your app:
angular.module('app',['app.my-directives']).config(...)-
angular.module('app.my-directives',[]).directive('SomeDirective',function(){})
Here I have a full example of a working application using angular-ui-route (I recommend you this routeProvider instead of the default provider, it's more expressive and robust), I hope it works for you, it has 2 states each one with different views, and several modules for directives and factories:
Images Gallery with AngularJS
Images Gallery with AngularJS Live
PS: ignore the web.js file, it's only for deploy the dist folder using Node.JS
I hope this solve your issue, please update the question with the plunker

Angular Form HTML from API with ng-model not binding

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

Accessing Restangularized Data

I am using Restangular in my Angular app. I can successfully pull data from by backend. However, I am having difficulty getting it to update the data after submitting a form. The PUT method will work and will send the request to the API but the payload isn't correct. It is sending the original data. I can't seem to get the restangularized data to show up on my page.
I can make the response for my data available in the views by doing:
$scope.user = Restangular.one('user', 1234).get().$object;
The user information does not show up unless I put in $object.
If I just use:
$scope.user = Restangular.one('user', 1234).get();
My views don't display the user information, putting {{user}} will be {"restangularCollection":false,"reqParams":null,"parentResource":null}.
As I understand it, Restangular appends the different functions in order to enable the restful actions which is why Restangular.one('user', 1234).get().$object wouldn't be preferable.
I tried putting in setResponseExtractor() into my config per the documentation (https://github.com/mgonto/restangular#how-can-i-access-the-unrestangularized-element-as-well-as-the-restangularized-one) but it is overwritten by setResponseInterceptor() which I need to transform the response from getList() into an array.
Here is my controller:
props.controller('UserEditController', function($scope, $routeParams, $location, Restangular) {
var UserEdit = Restangular.one('users', $routeParams.userId).get();
$scope.user_edit = Restangular.copy(UserEdit);
console.log($scope.user_edit);
$scope.save = function() {
$scope.project.put().then(function() {
$location.path('/');
});
});
My form:
<h1>Edit Profile</h1>
<form ng-submit="save_user()">
<label for="first_name">First Name:
<input type="text" name="first_name" ng-model="user_edit.first_name" ng-value="user_edit.first_name" /><br />
<label for="last_name">Last Name:
<input type="text" name="last_name" ng-model="user_edit.last_name" ng-value="user_edit.last_name"/><br />
<label for="email">Email:
<input type="text" name="email" ng-model="user_edit.email" ng-value="user_edit.email"/><br />
<label for="position">Position:
<input type="text" name="position" ng-model="user_edit.position" ng-value="user_edit.position"/><br />
<button type="submit">Save</button>
Cancel
</form>
I've looked over the Restangular example on Plunker (http://plnkr.co/edit/d6yDka) and it got me aways but I can't seem to figure this out. It is driving me crazy. Can someone help?
This only addresses the .$object portion of the question.
Restangular does everything with promises. Before the response data can be accessed, the promise has to be fulfilled/resolved.
// Assign data to $scope once response is returned.
Restangular.one('users', $routeParams.userId).get().then(function(user){
$scope.user_edit = user;
});
Then putting {{user_edit}} in your view should display the user data.

From AngularJS how to pass data from a controller+template to another controller+template?

I'm building an application with AngularJS and Slim PHP framework for the backend, now I completed my 1st form and created a route to the template for it. Now my problem arise when I want to pass my data to another controller, I need to pass the data to another controller+view(template), I don't want to pollute my first view neither the controller of it and so I really want/need to pass the data to another controller which I could play with my data and another form (the 2nd form is for calculation and other stuff)...So you can call the first controller a pre-save, while the real data save (backend to DB) will only happen in the second controller+template. Here is a short of my 1st template view that has the form:
<form novalidate id="formAdd" name="formAdd" class="form-horizontal well col-md-7 col-md-pull-5" method="post">
<fieldset>
<legend>Transaction form</legend>
<div class="form-group">
<label for="symbol" class="col-sm-4 control-label">Symbol</label>
<div class="col-sm-5 symbols">
<input type="text" name="symbol" class="form-control" ng-model="trsn.symbol" placeholder="symbol" required />
</div>
</div>
<div class="form-group">
<label for="accnt_id" class="col-sm-4 control-label">Account</label>
<div class="col-sm-5">
<select id="accnt_id" name="accnt_id" ng-model="trsn.accnt_id" class="form-control" required>
<option value="">...</option>
<option ng-repeat="account in trsn.accounts" value="{{account.accnt_id}}">{{account.accnt_name}}</option>
</select>
</div>
</div>
<!-- ....etc, etc.... -->
<div class="form-actions col-sm-8 col-sm-offset-4">
<button type="submit" name="save_btn" class="btn btn-primary" ng-disabled="formAdd.$invalid" ng-click="preSaveTrsn(trsn, formAdd)">Save transaction</button>
<button type="reset" class="btn btn-default">Cancel</button>
</div>
</fieldset>
</form>
then the app with the module and routes:
var investingApp = angular.module('investingApp', ['ngSanitize','ngResource', 'ngRoute'])
.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/new-trsn',
{
templateUrl: 'templates/StockTransaction.html',
controller: 'StockTransactionController'
});
$routeProvider.when('/presave-trsn',
{
templateUrl: 'templates/PreSaveTransaction.html',
controller: 'PreSaveTrsnController'
});
});
now inside my first controller is the presave function, which is empty since I don't know what to do with it so that I can send the transaction data to the next controller+view:
investingApp.controller('StockTransactionController',
function TransactionController($scope, $http, $location, $compile, $timeout, transactionDataService, dateFilter) {
// define some default variables
$scope.trsn = {};
$scope.trsn.symbol = "";
...
$scope.preSaveTrsn = function(trsn, formAdd) {
// what to put in here to transfer data to next controller????
};
and then my last controller, I have also nothing in there yet since I can't receive any data....but basically what I want to inject is the transaction data (trsn) which comes from 1st form/controller.
investingApp.controller('PreSaveTrsnController',
function MenuController($scope, $http, trsn) {
console.debug(trsn);
});
Does I have to put something inside the routeProvider somehow? ...or does I have to fill in something special inside the preSaveTrsn function inside my 1st controller??? I'm quite confused with this since all example I find are for saving right away to database, but I can't do that the way I build my app, it really has to be on the second controller for few reasons which I don't think I have to explain here.... Thanks for any help given :)
You may create a lightweight service - value
angular.module('investingApp').value('MySharedValue', {});
And then inject it in both controllers:
TransactionController($scope, $http, $location, $compile, $timeout, transactionDataService, dateFilter, MySharedValue)
And just to assign your shared value to it
$scope.preSaveTrsn = function(trsn, formAdd) {
MySharedValue.trsn = trsn;
};
There are 2 ways to achieve it. First is to declare your model object on $rootScope or on a scope which is parent to both of these controller scope. This way the data gets shared and the changes are available to both controller, irrespective of who makes it.
The second better approach is to create a service which tracks the model update. Inject this service into both the controller. Any controller can ask ask for the model from the service and update it. Since services are singleton, the model changes are shared across controller.
Like
angular.module("myApp",[]).factory('transactionService',[function(){
var service={};
var model={};
service.preSaveTrsn = function(trsn, formAdd) {
//set model here
};
service.getPreSaveTrsn=function() {
return model;
}
return service;
}]);

Resources