Angularjs Directive Not Updating from Service - javascript

I'm new to Angularjs and I'm trying to build a simple location finder widget. I have a service set up to contain the location data in a json block. Then I have a controller for the search form that makes a http call to get the json. Then I update the data in service. The service is also used in the location results controller to set the data for the directives on the front end. I've tried a bunch of different things and I'm not sure what I'm doing wrong. Thanks!
(function() { // start closure wrap
var app = angular.module('store_locator', []);
app.service('$store_location_data', function() {
var store_location_data = this;
store_location_data.data = [];
store_location_data.update_data = function(data) {
store_location_data.data = data;
}
});
app.controller('StoreLocatorFormControllor', [ '$http', '$store_location_data', function($http, $store_location_data) {
this.search_form = {};
this.searchLocations = function() {
$http.get('services/locations/').
success(function(data, status, headers, config) {
$store_location_data.update_data(data);
}).
error(function(data, status, headers, config) {
alert('fail');
});
this.search_form = {};
} // end form submit
}]);
app.controller('location_results', [ '$store_location_data', function($store_location_data) {
this.locations = $store_location_data.data;
}]);
})(); // end closure wrap
HTML:
<form name="storeLocatorForm" ng-controller="StoreLocatorFormControllor as storeLocFormCtrl" ng-submit="storeLocFormCtrl.searchLocations()">
<p>
<input ng-model="storeLocFormCtrl.search_form.zip_code" type="text" name="zip_code" value="" />
</p>
<p>
<select ng-model="storeLocFormCtrl.search_form.distance">
<option value="20">20</option>
<option value="40">40</option>
<option value="60">60</option>
</select>
</p>
<p><input type="submit" value="Submit" /></p>
</form>
<div ng-controller="location_results as results" ng-show="results.locations.length">
<div ng-repeat="location in results.locations">
<h1>{{ location.name }}</h1>
</div>
</div>

You are directly assigning service data at the time of loading controller.
You shouldn't do that because when you are assigning data from service the ajax call may just started or might not, but for sure it hasn't completed. So for that reason you service data is empty always.
I'd suggest you to use $broadcast, this will be useful in your case.
When you get the locations data inside your controller you will $broadcast event inside your controller, and that will listen by the controller whichever listing to that event using $on
StoreLocatorFormControllor
app.controller('StoreLocatorFormControllor', [ '$http', '$rootScope', '$store_location_data', function($http, $rootScope, $store_location_data) {
this.search_form = {};
this.searchLocations = function() {
$http.get('services/locations/').
success(function(data, status, headers, config) {
$store_location_data.update_data(data);
$rootScope.$broadcast('locationFetched');
}).
error(function(data, status, headers, config) {
alert('fail');
});
this.search_form = {};
} // end form submit
}]);
location_results
app.controller('location_results', [ '$store_location_data', function($store_location_data) {
//will call when locationFetched event gets broadcast
$rootScope.$on('locationFetched', function(event, data){
this.locations = $store_location_data.data;
});
}]);
Hope this will be helpful to you. Thanks.

Related

Creating an edit form

I am trying to create an edit from in my angularjs application but I am having some difficulty. I just do not know how to go about it.
This is the state that takes you to the form:
ui-sref="system.institutions.edit({ id: i.id })"
Now when the form loads I did this:
<form role="form" name="editInstitutionForm" ng-submit="editInstitution()" ng-init="getInstitutionInfo()">
...
</form>
The function getInstitutionInfo() is supposed to load the data from the server into the individual form fields.
This is the function:
$scope.getInstitutionInfo = function() {
InstService.show()
.success(function (data, status, headers, config) {
if (data.institutionInfo != undefined)
{
$scope.institutionInfo = data.institutionInfo;
}
})
};
This is the show method in my service:
InstService.show = function() {
var url = '/api/v1'+window.location.pathname;
url.replace('#', '');
return $http
.get(url)
};
Now my problem is how to pass the id from the state in the view through my controller to my http service.
Use $stateParams to access the id you are providing in the transition to the state here: ui-sref="system.institutions.edit({ id: i.id })"
Example:
.controller('InstEditCtrl', function ($scope, $state, $stateParams, InstService) {
var id = $stateParams.id;
});

AngularJS $watch not updating my output

I have an HTML which looks like -
<div ng-controller="PostsCtrl">
<ul ng-repeat="post in posts" style="list-style: none;">
<li style="padding: 5px; background-color: #f5f5f5;">
<h4>
{{post.postTitle}}
</h4>
<div class="post-details" ng-show="showDetails">
<p>{{post.postContent}}</p>
</div>
</li>
</ul>
</div>
Now the data is being populated from a JSON based REST URL and being displayed. I also have a form that will be adding new post to the database-
<form data-ng-submit="submit()"
data-ng-controller="FormSubmitController">
<h3>Add Post</h3>
<p>
Title: <input type="text" data-ng-model="postTitle">
</p>
<p>
Content: <input type="text" data-ng-model="postContent">
</p>
<p>
Tags: <input name="postTags" data-ng-model="postTags" ng-list
required>
</p>
<input type="submit" id="submit" value="Submit" ng-click="loadPosts()" /><br>
</form>
I basically want to achieve two things -
1. As soon as i add new post it shows up in the list of posts above.
2. As soon as i manually add a new post in the backend, front end automatically updates.
Is it possible to achieve both using angular and if yes how will i be able to do that.
Below is my controller code, which as of now is showing me existing posts as well as letting me add new post to DB.
<script>
var app = angular.module("MyApp", []);
app.controller("PostsCtrl", function($scope, $http) {
$http.get('http://localhost:8080/MyApp/posts')
.success(function(data, status, headers, config) {
$scope.posts = data;
}).error(function(data, status, headers, config) {
console.log("Error in fetching the JSON data.");
});
$scope.$watch('posts', function(newVal, oldVal){
console.log('changed');
alert('hey, myVar has changed!');
}, true);
/*$scope.$watch('posts', function() {
alert('hey, myVar has changed!');
console.log("test log");
$scope.$digest();
});*/
});
app.controller('FormSubmitController', [ '$scope', '$http',
function($scope, $http) {
$scope.loadPosts = function() {
$http.get('http://localhost:8080/MyApp/posts')
.success(function(data, status, headers, config) {
$scope.posts = data;
alert(JSON.stringify(data));
//$scope.posts_updated = data;
}).
error(function(data, status, headers, config) {
console.log("Error in fetching the JSON data.");
});
}
$scope.list = [];
$scope.submit = function() {
var formData = {
"postTitle" : $scope.postTitle,
"postContent" : $scope.postContent,
"postTags" : $scope.postTags,
"postedBy" : "admin"
};
var response = $http.post('addPost', formData);
response.success(function(data, status, headers, config) {
console.log("na");
});
response.error(function(data, status, headers, config) {
alert("Exception details: " + JSON.stringify({
data : data
}));
});
//Empty list data after process
$scope.list = [];
};
} ]);
</script>
Any help on this will be really appreciable.
1: on your success of post, you can just push the added object into your posts list. This will trigger the two-way-binding, and the object will "automatically" appear in your ng-repeater.
$scope.posts.push(element);
2: This one is a bit tricky, since angular is a client-side application, it doesn't recognize what happens on the server-side. What you have to do to make this work is to look at websockets (like SignalR or similar) that can make a push to your client whenever something gets added. This also depends on that your "manual" insert is done using a programatically method. Doing it directly from database-changes is going to be alot more painfull
Initialize $scope.posts before invoking $http request
$scope.posts = [];
Since you are using $http service, it should automatically repaint ng-repeat when new data found. So you don't need be to worried about it
Very important thing is that you don't need to call $digest when you use $http service. Using $digest blindly is a very bad practice and is major performance issue. In the end of $http service angular automatically call $digest so you don't need to call again

Rendering a list of json content fetched from an API via angular js

Angular newbie here, Trying to render a list of json content in the template. Using Angular 1.0.5.
Here is the app.js
var Task = angular.module("Task", ["ui.bootstrap", "ngCookies"]);
Task.config(function ($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "static/js/app/views/todolist.html",
controller: "TaskListController",
resolve: {
tasks: function (ToDoService) {
return ToDoService.list();
}
}
})
........
Here is the TaskListController:
Task.controller('TaskListController', function ($scope, GlobalService, ToDoService, tasks) {
$scope.tasks = tasks;
console.log("tasks");**// this does not get logged. Any reason why?**
$scope.globals = GlobalService;
});
ToDoService -
Task.factory('ToDoService', function ($http, $q) {
var api_url = "/todo/";
return {
list: function () {
var defer = $q.defer();
$http({method: 'GET', url: api_url}).
success(function (data, status, headers, config) {
defer.resolve(data);
}).error(function (data, status, headers, config) {
defer.reject(status);
});
return defer.promise;
},
And the view goes here -
<div class="content" ng-repeat="task in tasks">
<a class="clip-open" href="{{task.api_url}}">
<h3>{[{task.title}]}</h3>
</a>
<div class="meta clips-note">
<p>
{{task.description|truncate:150}}
</p>
</div>
<p>
<small><abbr time-ago title="{{post.assigned_time}}" class="date"></abbr></small>
<small><b>by</b> {{task.kreator}}</small>
</p>
</div>
</div>
</div>
The server at /todo/ is returning a list of todos in JSON format. But in my browser, I can't see the data. What am I missing?
Please ignore all missing semi-colons and missing parenthesis.
Assume all the files have been imported.
The name of your resolve is "todos"
resolve: {
todos: function (ToDoService) {
return ToDoService.list();
}
}
However, you are injecting tasks into your controller (which I don't see defined):
Task.controller('TaskListController', function ($scope, GlobalService, ToDoService, tasks) {
$scope.tasks = tasks;
$scope.globals = GlobalService;
});
I think you need to inject todos into your controller to access them.

Call angular controller's method from outside

Here's the code: http://jsbin.com/rucatemujape/1/edit?html,js,console,output
My question is how do I manually call method changeUser from JavaScript so the output HTML changes?
I can do this by executing (relies on jQuery)
angular.element('body').scope().changeUser({fullName: 'John Doe'});
angular.element('body').scope().$apply()
But I want to know is there any better way?
For example, in knockout.js I can execute viewModel.someFunction() any time and knockout correctly handles this.
Why do I want to do this: because I want be able to change model from browser's console when debugging a code.
Edit: Another reason why I need this it's getting information from Restful Services and updating a model. Yes I can trigger that event by clicking a button with "ng-click" attribute but how to deal with events which are not initiated by user? For example, repeating ations from setInterval or something
var myApp = angular.module('myApp', []);
myApp.controller('MyController', function($scope, $timeout) {
$scope.user = {};
$scope.count = 0;
$scope.changeUser = function(user) {
$scope.user = "MyName";
$scope.count++;
// call function after 1 sec.
$timeout($scope.changeUser, 1000);
};
// initiate function
$scope.changeUser();
});
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.min.js"></script>
</head>
<body ng-controller="MyController"
<span>Hello, {{user}}</span>
<span>count, {{count}}</span>
</body>
</html>
Use ng-click="changeUser()" to call the fucntion
http://jsbin.com/rucatemujape/2/edit
Controllers do not really expose "APIs" outside their own 'scope' (and below). So either you do the Ajax call within the controller or you expose the "user data" to display.
For example: Kostia Mololkin's answer's (in main comments) uses a global variable to share the data.
Angular Way
Here I present a more "angular way": the user is being handled by a "Service". Not clear which direction you wanted exactly. Anyway, this cannot hurt.
Angular works best when you use "data" to "communicate state" instead of "events". So the idea again is to arrange things so your data instance (i.e. "user") is the same between "{{user.fullname}}" and where the Ajax call is taking place. There are many ways to skin a cat and it highly depends on the needs of your application. For example, if you build a singleton service for the ajax call, you can also have the data own by it AND exposed to the controller in some form or another (again, many ways of doing this).
NOTE: If you use angular's system to perform ajax calls (i.e. $http or $resource for instance) then you should never need to manually call "$apply()". Btw, you should "wrap" calls with $apply() instead of calling it "afterwards" reason being to properly handle "throws".
Plunker example with Ajax/Rest call:
http://plnkr.co/edit/SCF2XZCK5KQWkb4hZfOO?p=preview
var myApp = angular.module('myApp', []);
myApp.factory('UserService', ['$http',function($http){
// Extra parent object to keep as a shared object to 'simplify'
// updating a child object
var userData = {};
userData.user = { fullName:'none' };
function loadUserData(userid) {
$http.get('restapi_getuserdata_'+userid+'.json').
success(function(data, status, headers, config) {
// update model
userData.user = data;
}).
error(function(data, status, headers, config) {
console.log("error: ", status);
});
}
return {
userData: userData,
loadUserData: loadUserData
};
}]);
myApp.controller('MyController', ['$scope', 'UserService', '$timeout',
function($scope, UserService, $timeout) {
// shared object from the Service stored in the scope
// there are many other ways, like using an accessor method that is
// "called" within the HTML
$scope.userData = UserService.userData;
}]);
myApp.controller('SomeOtherController', ['UserService', '$timeout',
function(UserService, $timeout) {
// $timeout is only to simulate a transition within an app
// without relying on a "button".
$timeout(function(){
UserService.loadUserData(55);
}, 1500);
}]);
HTML:
<html ng-app="myApp">
...
<body ng-controller="MyController">
<span>Hello, {{userData.user.fullName}}</span>
<!-- simulating another active piece of code within the App -->
<div ng-controller="SomeOtherController"></div>
...
A variant using a getter method instead of data:
http://plnkr.co/edit/0Y8gJolCAFYNBTGkbE5e?p=preview
var myApp = angular.module('myApp', []);
myApp.factory('UserService', ['$http',function($http){
var user;
function loadUserData(userid) {
$http.get('restapi_getuserdata_'+userid+'.json').
success(function(data, status, headers, config) {
console.log("loaded: ", data);
// update model
user = data;
}).
error(function(data, status, headers, config) {
console.log("error: ", status);
user = undefined;
});
}
return {
getCurrentUser: function() {
return user || { fullName:"<none>" };
},
userLoggedIn: function() { return !!user; },
loadUserData: loadUserData
};
}]);
myApp.controller('MyController', ['$scope', 'UserService', '$timeout',
function($scope, UserService, $timeout) {
// getter method shared
$scope.getCurrentUser = UserService.getCurrentUser;
}]);
myApp.controller('SomeOtherController', ['UserService', '$timeout',
function(UserService, $timeout) {
// $timeout is only to simulate a transition within an app
// without relying on a "button".
$timeout(function(){
UserService.loadUserData(55);
}, 1500);
}]);
HTML:
<html ng-app="myApp">
...
<body ng-controller="MyController">
<span>Hello, {{ getCurrentUser().fullName }}</span>
<!-- simulating another active piece of code within the App -->
<div ng-controller="SomeOtherController"></div>
...

Connect Models in Angular.js

I'm just getting started with Angular.js and I'm not sure how to "link" two "models" together. I have the following code in my index.php file
<div ng-controller="AccountCtrl">
<h2>Accounts</h2>
<ul>
<li ng-repeat="account in accounts">
<span>{{account.id}} {{account.ownedBy}}</span>
</li>
</ul>
</div>
<div ng-controller="TransactionCtrl">
<h2>Transactions</h2>
<ul>
<li ng-repeat="transaction in transactions">
<span>{{transaction.id}} {{transaction.timestamp}} {{transaction.amount}} {{transaction.description}} {{transaction.account}}</span>
</li>
</ul>
</div>
and the following js
function AccountCtrl($scope, $http) {
// initialize Data
$http({
method:'GET',
url:'http://api.mydomain.ca/accounts'
}).success(function(data, status, headers, config) {
$scope.accounts = data;
}).error(function(data, status, headers, config) {
alert('Error getting accounts. HTTP Response status code: '+status);
});
}
function TransactionCtrl($scope, $http) {
// initialize Data
$http({
method:'GET',
url:'http://api.mydomain.ca/transactions'
}).success(function(data, status, headers, config) {
$scope.transactions = data;
}).error(function(data, status, headers, config) {
alert('Error getting transactions. HTTP Response status code: '+status);
});
}
So in my example each account will have many transactions and I want to add a function to my account controller to calculate the balance of the account based on the transactions but I'm not sure how to do that because they are in different $scopes.
Is there a way to do this in Angular or do I have to return the "linked" transaction information in my JSON response from the server when I get the accounts?
I guess account holds transactions, right?
Then I guess, you can create an service to manage account / transaction data.
Inject this service into both controllers.
module = angular.module('app', []);
module.factory('accountService', function($http) {
var obj = {
// handles http communication to/from server.
// also has methods/getters/setters for data manipulation, etc.
};
return obj;
});
module.controller('AccountCtrl', function($scope, accountService) {
// access accountService for the view-databind.
});
module.controller('TransactionCtrl', function($scope, accountService) {
// access accountService for the view-databind.
});
Since you are making both http requests at the same time, I would change my service to return the transactions as a property of the account object. Then it would be one server call, less overhead, and you data would be in the format that you need it in. I think you have the right idea with your last question.
And good choice using Angular. If you haven't found them yet, John Lindquest released a great set of videos at egghead.io.

Categories

Resources