Angular Factory Manipulating Data (CRUD) with function in the Factory - angularjs

I have a simple Angular App that uses a factory to pull a list of items from a "JSON" file. Eventually this will be connected to a database but for now I'm starting with just pulling from a static file. The JSON file contains an array of items. I'm really trying to understand how do I get a reference to my data in the factory after the promise has been returned to the controller.
My Factory is setup as follows:
angular.module("myApp").factory("ServiceTypeFactory", ['$http', function ($http ) {
return {
ServiceTypes: function () {
return $http.get('json/servicetypes.json').success
(
function (data) {
return data;
});
},
find: function (id) {
return _.findWhere(data, {ID:id}); // Not sure how to get access to the data
}
}
}]);
This works great I can share the Factory across multiple controllers. The part I'm missing is how do I reference a specific array item from my json file and update it in my controller. I'm not following how to get a reference to the actual data so when I modify the item in one controller it the change would be reflected in another controller.
In both of my controllers I have the following code to get a reference to the data initially.
var popService = function (data) {
$scope.ServiceTypes = data;
}
// IF at ANY time the Service Types have not been loaded we will populate them.
if (typeof $scope.ServiceTypes === "undefined") {
ServiceTypeFactory.ServiceTypes().success(popService);
}
My understanding is my $scope.ServiceTypes has really a reference to the data. How do I back in my factory in a function get access to the actual single source of my data. I get that the factory returns the data with functions an object but I'm missing how to reference this data back in my factory to manipulate it. In the future I want to perform CRUD operations on it for the time being I'm just trying to work out the mechanics.
What my JSON file looks like:
{
"serviceTypes": [
{
"ID": "1001",
"ServiceTypeName": "111111",
"Description": "aaaaaaa"
},
{
"ID": "1002",
"ServiceTypeName": "222222",
"Description": "bbbbbbb"
},
{
"ID": "1003",
"ServiceTypeName": "3333333",
"Description": "ccccccc"
},
{
"ID": "1004",
"ServiceTypeName": "444444",
"Description": "dddddddd"
},
{
"ID": "1005",
"ServiceTypeName": "5555555",
"Description": "eeeeeee"
}
]
}

Just needed to clean the code up a little. Watching a couple of quick example on egghead.io made it clear.

Related

AngularJS with Hypermedia (HATEOAS): how to use hypermedia urls accross states

I have an AngularJS application with ui router that consumes a REST API with Hypermedia. The general idea is to have the API generate the urls for its various calls, and keep the client from constructing the urls itself.
For example, when fetching the list of products, here's what the API returns:
[
{
"id": 1,
"name": "Product A",
"_links": {
"self": {
"href": "http://localhost:4444/api/products/1",
"name": null,
"templated": false
},
"actions": []
}
},
{
"id": 2,
"name": "Product B",
"_links": {
"self": {
"href": "http://localhost:4444/api/products/2",
"name": null,
"templated": false
},
"actions": []
}
}
]
So, the problem: I want to navigate to the detail of a product. I have the API url available from the collection hypermedia. However, upon changing states, I somehow need to pass this url to the detail state, so that the controller of the detail state can fetch the product.
The UI urls are completely decoupled from the API urls, i.e. the client has its own url scheme.
What's the best way to achieve this, all the while keeping the client stateless and each page bookmarkable?
One solution is to pass the url by ui router's data property. However, the page wouldn't be bookmarkable. Another way is to pass the API url in the UI url, which would keep the page bookmarkable (as long as the API url doesn't change), but the UI url would be very ugly.
Any other thoughts?
Unless I'm very wrong about this, I'm not looking for a templated solution, i.e. a solution where the API returns a template of a url that needs to be injected with parameters by the client. The whole point is that the url is already populated with data, as some urls are quite a bit more complicated than the example provided above.
I've encountered this problem a few times before. I've detailed my preferred solution step-by-step below. The last two steps are specifically for your problem outlined in your post, but the same principle can be applied throughout your application(s).
1. Root endpoint
Start by defining a root endpoint on the API level. The corresponding root entity is a collection of top level links, in other words links to which the client(s) require direct access.
The idea is that the client only needs to know about one endpoint, namely the root endpoint. This has the advantages that you're not copying routing logic to the client and that versioning of the API becomes a lot easier.
Based on your example, this root endpoint could look like:
{
"_links": {
"products": {
"href": "http://localhost:4444/api/products",
}
}
}
2. Abstract main state
Next define an abstract super state that resides at the top of your state hierarchy. I usually name this state main to avoid confusion with the root endpoint. The task of this super state is to resolve the root endpoint, like:
$stateProvider.state('main', {
resolve: {
root: function($http) {
return $http.get("http://localhost:4444/api/").then(function(resp){
return resp.data;
});
}
}
});
3. Products overview state as child of the main state
Then create a products state which is a descendant from the main state. Because the root endpoint is already resolved, we can use it in this state to get the link to the products API:
$stateProvider.state('products', {
parent: 'main',
url: "/products",
resolve: {
products: function($http, root) {
return $http.get(root._links.products.href).then(function(resp){
return resp.data;
});
}
}
});
4. Product detail state as child of the products state
Lastly, create a product detail state as a child of the products state above. Pass in the product's id via the $stateParams (hence, it's part of the client URI) and use it to retrieve the correct product from the products array resolved in the parent:
$stateProvider.state('products.product', {
url: "/{productId}"
resolve: {
product: function($http, $timeout, $state, $stateParams, $q products) {
var productId = parseInt($stateParams.productId);
var product;
for (var i = 0; i < products.length; i++) {
product = products[i];
if (product.id === productId) {
return $http.get(product._links.self.href).then(function(response){
return response.data;
});
}
}
// go back to parent state if no child was found, do so in a $timeout to prevent infinite state reload
$timeout(function(){
$state.go('^', $stateParams);
});
// reject the resolve
return $q.reject('No product with id ' + productId);
}
});
You can move the code above into a service to make your states more lightweight.
Hope this helps!

Angular Js call multiple times controller method and display output in view

I am very new to angular js and trying to fetch results from a service in loop and want to show in View.
I believe the flow will be:
View calls controller with last got entry (controller shouldn't know user want to view more data or not so view will control when to stop.). Controller will be calling a service/factory to retrieve the results.
Please suggest if the workflow is correct or I'm mixing view with controller work. Or somehow I believe controller should control logic for how much data has to be collected and just update some local variable with it. View should use that variable to reflect it on view side.
Second thing: How I can retrieve results from Controller in loop from View.
Edit:
How I can get persons values from controller:
If i want to get persons from this controller. How should i ? I'm doing something wrong here. I am not sure if I should call methods from view?
angular.module('app.dashboard.dashboardControllers', []).
controller('dashboardController', ['$scope',
function ($scope) {
$scope.persons = $scope.showAssets(null);
$scope.showAssets = function (lastExecutedId) {
var persons_1 = [{
"id": 860,
"firstName": "Superman",
"lastName": "Yoda"
}, {
"id": 870,
"firstName": "Foo",
"lastName": "Whateveryournameis"
}, {
"id": 590,
"firstName": "Toto",
"lastName": "Titi"
}, {
"id": 803,
"firstName": "Luke",
"lastName": "Kyle"
}];
return persons_1;
};
}
]);
Your flow is correct: View -> Controller -> Service = Results last stored in Service
Second Thing: To get results from the view, such as user input elements, those elements need to be bound to some $scope inside the controller.
The power of Angular is you will never need to call for these values once you bind them correctly. Your view will always reflect the current values of $scope objects or whatever else has a bind on it.
Look this page for descriptions on the popular ways to bind elements:
http://www.javabeat.net/angularjs-ng-model-ng-bind/

Angulajs - how to extract certain data from json

I have this json example where I need to get data individually.
json:
[
{
"web": "63",
"mobile": "2525",
},
{
"web": "70",
"mobile": "1886",
},
{
"web": "65",
"mobile": "1044",
}
]
then in the controller:
myData.get ().then (function (data) {
$scope.data = data;//this is fine
console.log(data);
$scope.web = data.web//this does not work
$scope.mobile = data.mobile//this does not work
console.log($scope.data.web);//this does not work
console.log($scope.web);//this does not work
//and then lets say I want to pass them in the url
var myUrl = "www.myurl.com/"+$scope.web+$scope.mobile;
});
so basically I need to be able to work with individual data within the json file, so I can pass them them anywhere within the applications logic. What am I doing wrong?
heres plunker: http://plnkr.co/edit/Dre5j8yLDoJek89KCmak?p=preview
Many thanks for your help
You need to use the data object inside $scope. So, if you try the following, it should work properly. console.log($scope.data) or console.log($scope.data.web)
In your controller, define an empty array and then assign the response to it.
var localdata = [];
// http call goes here
localdata = response.data;
// access the required members of localdata array
// since it's an array, you need to access individual data elements with index
// for example:
console.log($scope.data[0].web)
Use
$scope.data = angular.fromJson(data);
console.log($scope.data);
and check

AngularJS - Cannot read property 'navigable' of undefined

My app.run:
app.run([
'$q', '$rootScope', '$state', 'settings', 'datacontext',
function ($q, $rootScope, $state, settings, datacontext) {
settings.currentLang = settings.languages[0];
breeze.core.extendQ($rootScope, $q);
datacontext.getSideMenuItems().then(function (data) {
var startUp = undefined;
var rootUrl = '/app/views/';
/// this is the upper level used for side menu only
angular.forEach(data, function (value) {
// now loop thru the data for the $state items
angular.forEach(value.viewStates, function (viewState, key) {
if (key == 0) {
startUp = viewState.name;
}
var state = {
"url": '/' + viewState.name,
"parent": viewState.parentName,
"abstract": viewState.isAbstract,
"views": {}
};
angular.forEach(viewState.views, function (view) {
state.views[view.name] = {
controller: view.controllerName,
templateUrl: rootUrl + view.templateUrl + '.html'
};
});
$stateProviderRef.state(viewState.name, state);
});
$state.go(startUp);
});
});
}
My Data: [as JSON representation, ignore the Capitalization, breeze renames them lowercase]
[{
"$id": "1",
"Id": 2,
"Icon": "fa-home",
"IsActive": "active",
"IsShared": false,
"OrderNum": 1000,
"Title": "Dashboards",
"FK_DbModuleId": 1,
"DBoardModule": null,
"ViewStates": [
{
"$id": "2",
"Id": 2,
"IsAbstract": false,
"Name": "PersDboards01",
"ParentName": "root",
"OrderNum": 1,
"FK_ViewGroupId": 2,
"ViewGroup": {
"$ref": "1"
},
"Views": [
{
"$id": "3",
"Id": 4,
"ControllerName": "MyDashboardCtrl",
"Name": "container#",
"TemplateUrl": "dashboards/myDashboard",
"FK_ViewStateId": 2,
"ViewState": {
"$ref": "2"
}
},
{
"$id": "4",
"Id": 5,
"ControllerName": "LeftPanelCtrl",
"Name": "leftPanel#",
"Title": "null",
"TemplateUrl": "shell/html/left-panel",
"FK_ViewStateId": 2,
"ViewState": {
"$ref": "2"
}
}
]
}
]
I know the Json has the properties capitalized but I am not really using JSON, I just copied this off of fiddler which got it from my Web API.
I understand that the error "Cannot read property 'navigable' of undefined" means I am not defining the child state after I define the parent state, but I cannot see where I am doing that.
Does anyone know a better way to structure the app.run?
UPDATE:
I found my error, see below.
Solved:
Indeed the error was due to my creating child states before establishing the parent state.
I looked at my database and noticed the parent state LAST in my que:
Id : 150
IsAbstract : 1
Name: "root"
ParentName: ""
.....
Had I made sure to make it first the problem would not have occured.
Id : 1
IsAbstract : 1
Name: "root"
ParentName: ""
.....
I recognize this situation will not help many other than as a reminder to make sure your parent state is the first state in your loop when creating dynamic states/views
The code you included above does not reference 'navigable' at all, so it cannot be the source of this error. Please either include the additional files where that reference is being made, or better still, post a Plunkr that illustrates the problem.
I'm posting this as an answer rather than a comment because there's an additional detail that may help you find the actual source of the error, and you may find that you don't need any further help when you find it. In Angular it's very common for error locations to be hard to find. Many of its mechanisms have "deep" calls stacks, especially when an error is triggered either by the injector or digest cycle. What you end up finding is that the error itself is happening somewhere else from where you think it is.
I think that it is likely you are encountering the same error reported here:
https://github.com/angular-ui/ui-router/issues/488
You appear to be using AngularJS with Angular UI Router and Breeze, all together. The error reported there is identical to yours, and if you re-examine your code and follow what the others reported on that thread I think you will find that your problem is fixable... but has little/nothing to do with your actual code above!
The issue here is related to wrong Upper and Lower case JSON property naming during the processing. There is a working plunker - which calls console.log(state) at the end of JSON processing.. to show the result (do not foget to start the console with F12)
A snippet comparison would be like this
angular.forEach(response.data, function (value) {
// NO viewStates
// but ViewStates
angular.forEach(value.ViewStates, function (viewState, key) {
if (key == 0) {
startUp = viewState.Name; // instead of name
}
var state = {
"url": '/' + viewState.Name, // instead of name
"parent": viewState.ParentName, // instead of parentName
"abstract": viewState.IsAbstract, // instead of isAbstract
"views": {}
};
// No views
// but Views
angular.forEach(viewState.Views, function (view) {
state.views[view.Name] = {
controller: view.ControllerName, // instead of controllerName
templateUrl: rootUrl + view.TemplateUrl + '.html'
};
});
$stateProviderRef.state(viewState.Name, state); // instead of name
console.log(state)
});
});
because the JSON is like that (see Upper case at the begining):
"ViewStates": [{
"IsAbstract": false,
"Name": "PersDboards01",
"ParentName": "root",
...
},
"Views": [{
"ControllerName": "MyDashboardCtrl", // ControllerName
"Name": "container#", // Name
"TemplateUrl": "dashboards/myDashboard", // Templateurl
...
The working plunker plunker (just loging the result into console) shows that the rest of the code should be working

Backbone Model not compatible with underscore and ASP.NET MVC Web API Controller?

This is a two stage problem when working with backbone.js and a web api controller.
I have a simple web api controller that returns a JSON string, in fiddler the result looks like this:
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading":"The heading"
}
I use the following code to fetch a user from my web api
var user = new Usermodel({ id: "1" });
user.fetch({
success: function (u) {
console.log(u.toJSON());
}
});
now my backbone user object looks like this
{
id: "1",
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading": "The heading"
}
}
When I try to bind this backbone model object to my view template that looks like this
<form>
<input type="text" value="<%=Heading%>" />
<input type="submit" value="Save" />
</form>
i get, Heading is undefined but when I use id it binds just fine? It seems like underscore does not like the backbone model object and just want a plain JSON object just like the one I get from my web api?
The second problem with this is that when I save my model with user.save({ Heading: "my new heading }); the payload to my web api is the backbone model which is completely wrong because my api expects a user object like this to be sent to the server:
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading":"The heading"
}
and not the backbone model with the real object wrapped inside. Is it possible to solve so that underscore can handle backbone models and tell backbone to only send the payload that my end point expects?
You may be able to solve the problem by following these steps:
In addition to using fiddler to inspect your response, look at the response on the network tab of Chrome Developer Tools. If the response does not look like this, then your web api is not returning a valid json response, the problem is most likely within your web api. You need to get/provide more information about your web api to solve the problem. Verify that the response looks like this:
After verifying that the response from the web api is correct, check out the following jsfiddle I modified:
http://jsfiddle.net/J83aU/23/
Fix your client side code referencing the example I have provided.
Properly instantiate the Backbone objects.
Call the view.render function at the correct step, after the response is received from the server.
Make sure that the main content div is actually rendered before creating a view which depends on it for the 'view.el' property.
Declare the 'view.el' property properly, with a string rather than jQuery object.
Use development Backbone and underscore to enable debugging, an important concept when learning to use open source frameworks such as Backbone.
Use jsfiddle's echo/json api to mock a valid ajax json response, exactly as described in step 1.
The following json example you submitted is not even valid json, if you update your question with valid json example, it would be easier to solve the problem. It is unlikely that Backbone created this non-json structure and more likely that you have submitted it here incorrectly.
{
id: "1",
{
"$type": "MvcApplication.Models.Article, MvcApplication",
"Id": "1",
"Heading": "The heading"
}
}
Finally, try to provide a screenshot of the http headers or something for the problem that is occurring when you call model.save().
Read over the Backbone documentation for model.save() and make sure you are doing everything just as the example provided.
You may be able to workaround Backbone's funky save function by forcing your attributes into POST parameters using ajax options:
$.fn.serializeObject = function(){
var o = {};
var a = this.serializeArray();
$.each(a, function() {
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
var saveView = Backbone.View.extend({
events:{
'click #saveSubmitButton':'submit'
},
submit:function (event) {
event.preventDefault();
var view = this,
attributes = $('#saveForm').serializeObject();
this.model.save(attributes, {
data:attributes,
processData:true,
success:function (model) {
//....
}
});
},
render:function () {
//.......
}
});
The attributes property of your model should be unaltered. Send those to your template call:
var MyModel = Backbone.Model.extend();
var newModel = new MyModel({
"$type": "MvcApplication.Models.Article, MvcApplication",
"Heading":"The heading"
});
var html = _.template(templateVar, newModel.attributes);
In your templateVar, which is your templated markup, you should be able to reference $type and Heading directly.
If you have a look at the jsFiddle through a debugger like Firebug you can see that the way you construct the model's URL is not working out, because the forward slash gets encoded. Can you try to modify your model declaration to this:
var Usermodel = Backbone.Model.extend({
url: function () {
return '/api/page/articles/' + this.get('id');
}
});
var user = new Usermodel({
id: '85'
});
And see if you still get the same JSON. Basically if you don't have a Backbone.sync override you are using built-in retrieval that for one shouldn't produce invalid JSON.

Resources