Best way to manage datas in an angularJS CRUD app - angularjs

First, i want to say that English is not my native language, so sorry by advance if i'm not 100% clear.
So let say i want to create a basic CRUD app for cars.
There would be the route "/cars" with the list of all cars.
To get those cars data, i would have to make a call to the API, which will send me all the data about all cars at once, and then display all the cars name. I think i'm right for now ?
The "problem" i have is when the user click on a specific car.
It should redirect the user to the route "cars/CAR_ID" and display this specific car's data. But how to get those data ? I mean, yes i could just make an API call "/api/car/CAR_ID", but why would i do that if i already have everything i need in the first api call with all the cars ?
I've been looking every thread, everywhere and they always make another api call, but it feel like a waste to me, i cant help it.
There got to be a reason why everyone does that and i'm missing it.
I mean, the other way would be to store the first call's data and instead of making the second api call, we could just fetch the data from the first call.
I'm new to web development so it may be better to make a request for every page than store a big set of data the first time and work with it all along the navigation.
If so, can i have some more insight on the subject, it feels like asking to the server something i already know...

A different answer to part of your question.
I mean, yes i could just make an API call "/api/car/CAR_ID", but why would i do that if i already have everything i need in the first api call with all the cars ?
What would happen if you didn't make the first api call? I mean, could't I directly type in my browser /api/car/12345?
Let's consider stackoverflow as an example, let's say I bookmark this question, and later on I open this page (lets assume I have no cached data) directly without going through the page with the questions list. In your scenario I would get a blank page because the creator would assume that I visit some other page first, which is unresonable (not always). Making an api call for each particular entity saves as from such a pitfall from the start because it doesn't assume anything about the browsing history of the user.

Here are the options I can think of.
1) Use ui-router (my preferred option). Here's an example route:
angular.module('carApp')
.config(function ($stateProvider) {
$stateProvider
.state('cars', {
url: '/cars',
templateUrl: 'app/cars/views/index.html',
controller: 'CarsCtrl',
resolve: {
cars : ['Car', function(Car) {
return Car.list();
}]
}
})
.state('cars.show', {
url: '/{carId}',
views: {
"#" : {
templateUrl: 'app/cars/views/show.html',
controller: 'CarCtrl'
}
},
resolve: { // the cars from the parent route (cars) will be injected into this route
car: ['$stateParams', 'cars', function($stateParams, cars) {
// not hitting the web service, find our car using lodash
return _.find(cars, {_id: $stateParams.carId});
}]
}
})
OR
2) Use caching in your services
angular.module('carApp')
.service('Car', function Car($http) {
return {
list: function() {
return $http.get('/cars', {cache: true});
}
};
});
The first time you call Car.list() it will call the web service. Subsequent calls to Car.list() will not call the web service. In your controller you can either call Car.list and find the correct car similar to the cars.show route or add another function to your service that can find a car by ID.

Related

url/state changes not working after angular.copy called in a factory

I'm at my wits end on this one, so any help is greatly appreciated!
I've been making an angular/node/mysql app for the past few weeks and came across this issue. I have a database running mysql that stores a list of users and their information. I created an API to provide the front-end with access to user data for login and all that. The flow is as follows:
User enters new state where info on them needs to be preloaded:
.state('info', {
url: '/info',
templateUrl: 'views/info.html',
controller: 'InfoController',
resolve: {
postPromise: ['user', function(user) {
return user.loadInfo();
}]
The code is run in the user factory so that data is fetched before the page loads:
app.factory('user', ['$http', 'auth', function($http, auth) {
var user = {};
user.loadInfo = function() {
var userId = auth.currentUser();
return $http.get('/users/' + userId).success(function(data) {
angular.copy(data, user);
});
};
return user;
}]);
The page loads fine the first time. Then I navigate somewhere else via $state.go('home') or something, and when I try to return to the same page (/info) it sometimes doesn't work. What happens is I enter in the new url, and the browser basically ignores it and just shows the url of my current page.
Start at localhost:8080/#/home
Type in localhost:8080/#/info and go there
Works fine first time. Navigate to localhost:8080/#/home
Now at localhost:8080/#/home
Type in localhost:8080/#/info, and the browser ignores it, just displaying localhost:8080/#/home
I've tried commenting out angular.copy, and that seems to fix the issue, but I still need to copy the data over that I fetch from the server (which does successfully get transferred).
So my primary question is: Is there an alternative to angular.copy that I can use that will work here? I still find it weird that it is used in all the examples but is causing this issue here.
Secondary question: If angular.copy is the way to go, have any of you seen this issue and have a guess at what's causing it? Does something look off in what I'm doing?
Bonus Info: I don't know how quite to describe this, but sometimes if I wait long enough before going back to the problem url, it works... I notice that a call is made to the server right as I'm typing in the problem url (before even hitting enter) and when that happens, it seems work. It's intermittent though, and stops any $state.go() calls from working to that problem url if they are called through a button click.
Alright, I got the answer for this one and it's pretty dumb. "user" is an object inside of the factory and "loadInfo" is a function attached to user. When I did the angular.copy(), data from the server overwrote the loadInfo function so I couldn't use it again...
So yeah, that's solved.

Multiple states and urls for the same template

I'm building a web application and I have a screen that consists in five sections, each section represents a level, the areas are the higher level of my tree, when I click in any card of the area, the system should return the skills of that area and so on.
I need to change the url and state according what the user is accessing, for example, if the user access some skill, the url must be
example.com/#/curriculum/skill/<skillId>
and if I access this link it should automatically load the capabilities from this skill and his parent which is area in this case.
I have one controller for area, skill, capability, knowledge and criteria, in each controller I have a action to load the next level of the tree, which looks like that
$scope.loadSkills = function (id) {
Area.loadSkills(...)
$state.go('curriculo.skill', {id: this.current.id}, {nofity: false, reload: false});
}
And these are my states
$stateProvider
.state('curriculum', {
url: '/curriculum',
templateUrl: '/templates/curriculo.html',
})
.state('curriculum.are', {
url: '/area/:id',
template: '',
})
.state('curriculum.skill', {
url: '/skill/:id',
template: '',
})
.state('curriculum.capability', {
url: '/capability/:id',
})
.state('curriculum.knowledge', {
url: '/knowledge/:id',
})
.state('curriculum.criteria', {
url: '/criteria/:id',
});
I'm new in Angular and I not sure about what to do, should I created multiple nested views in this case, and if so, how do I load stuff that I need according the url?
I would suggest to use the capability of multiple named views offered by the ui-router. You can read more about it here. Basically the documentation says the following:
You can name your views so that you can have more than one ui-view per
template.
If you check the example in the documentation, you'll notive that there are similarities between your scenario and the example, because you want to dynamically populate a different views (here named views).
Example
I tried to recreate your scenario in this JSFiddle.
First I created an abstract state which provides the different views like areas, skills etc. This is the template for the abstract state:
<div class="curriculum" ui-view="areas"></div>
<div class="curriculum" ui-view="skills"></div>
Next I created a nested state curriculo.main, which declares the different views (areas, skills etc.) you need. Each view has its own template and controller. Notice that the nested state has a resolve which initially loads the areas from a service called curriculo. If you use resolves remember that the resolve keyword MUST be relative to the state not the views (when using multiple views).
Basically the service is responsible for the business logic, means getting the areas, skills etc. In the JSFiddle I have hard-coded the HTTP results. Replace that with HTTP calls and make use of promises. Since each named view has its own controller we need a mechanism to notify about changes, for example to notify the SkillsController that skills have been loaded. Thus, I created a simple event system (subcribe-notify):
.factory('notifier', function($rootScope) {
return {
subscribe: function(scope, callback, eventName) {
var handler = $rootScope.$on(eventName, callback);
scope.$on('$destroy', handler);
},
notify: function(eventName, data) {
$rootScope.$emit(eventName, data);
}
};
});
The SkillsController can then subscribe to a specific event like so:
notifier.subscribe($scope, function(event, data) {
$scope.skills = data;
}, 'onSkillsLoaded');
The curriculo service calls (at the end of the getSkills()) notifyand provides an event. In this case the same event as you subscribed to in the SkillsController.
notifier.notify('onSkillsLoaded', result);
All in all, that's the magic behind my little example. It's worth mentioning that you need to apply best practices to the code, since this is just to recreate your scenario. For best practices I suggest the Angular Style Guide by John Papa.
Update 1
I updated my example to show you deep linking. I simulate the deep link via
$state.go('.', {area: 2, skill: 5});
This way I can activate a certain state. Now each view has its activate function. Inside this function I do all the work that is neseccary for the initialization, e.g. selecting an area if the query param is set. As you know, you can access the params with the $state service. I had to use a $timeout to delay the init of the areas controller because the subscribe wasn't made yet. Please try to find a better solution to this problem. Maybe you can use promises or register each controller in a service which resolves if all controller have been initialized.
If anything has been selected I also use the go with an additional option to set the notify to false.
$state.go('.', {area: area.id, skill: skillId ? skillId : undefined}, {notify: false});
If notify is set to false it will prevent the controllers from being reinitialized. Thus you can only update the URL and no state change will happen.

Angular choose html file based on url parameter

I don't know very much about angular, and I'm trying to get the hang of best practices regarding the use of URL routing and states and whatnot. I've got a scenario. It's a simple question, but I'm using it to get a handle on what's available to me.
Say that I have two completely separate web pages for displaying information on Ford cars and Toyota cars. When you access the pages, all you have is the car ID number, so you just hit the url "cars.com/info/id:198273918273". What's the best way, using angular.js, to immediately strip the id number from the url, use it to look up the car make, and display the appropriate html page, all without changing the url displayed at the top of the browser?
you can use functions in your route templateUrl
.when('/your_url/:car_id', {
templateUrl: function(attrs){
//Example of the login yours will be complex i guess :P
if(attrs.car_id == 1) { return 'template_ford.html' }
if(attrs.car_id == 2) { return 'template_toyota.html' }
},
controller : 'someController'
})
and by that your template can be chaged before the page is rendered and no need to send the user to a different url
Assuming that you are using angular-ui router
$stateProvider
.state('car-get', {
url:'/info/{car_id}/',
controller: ['$scope','$stateParams',function($scope,$stateParams){
var car_id = $stateParams.car_id;
// now you can get your car info
// and bind it to your template.
$scope.car = myservice.getcarinfo(car_id) //Here goes your service or your api calls to get car info
}],
templateUrl: 'path_to_detail_car_page.html',
});
Here is a good begginer tutorial for Angular-ui Router

How do you update a cached resource in an angular service?

What is a good pattern for updating angular data from a ngResource service that has been cached?
I been trawling posts like this one [1]: How to refresh / invalidate $resource cache in AngularJS, but would be good to hear from angular experts on the right approach for this specific (but pretty general) scenario.
I am looking for a general pattern here. Both in understanding and in implementing angular - I am a novice at it.
I have a pretty standard ngResource service that has a very standard query method, and a custom put method update.
myServices.factory('ThingsService', [
'$resource',
function ($resource) {
return $resource('/api/things/:id', { id: '#id' }, {
query: { method: 'GET', isArray: true, cache: true },
update: {method: 'PUT', cache: true },
});
}]);
I am using it from a controller like this:
$scope.things = ThingsService.query(function (x) {
// must assign these only once data is loaded
$scope.allCount = x.Things.length;
$scope.incomingCount = $filter('filter')(x.Things, { State: 'incoming' }).length;
});
So far so good. The data is returned just fine, and it renders nicely in a dashboard view.
We support in-place-editing and the user can edit the data right there in the dashboard list.
First take a shadow copy of the thing using angular.copy(...) so that we can support buffering of the changes for the user. (just like a dialog box does for a user). Then when they confirm their changes, we call with the shadow copy:
ThingsService.update({ id: currentThing.Id }, { Data: currentThing.Data }, function () {
//TODO: now, if this PUT succeeds,
//I want to update the value of $scope.things array to reflect the changes,
//without going to back to the server for the whole array.
});
This correctly PUTS the changes to the server, which returns an updated thing, but the dashboard view which is bound to the query method is not updated auto-magically. Was kind of hoping angular and the ThingsService and its cache would take care of that for me somehow, you know by updating the cached data. Since the service should know that I just updated one of the items that the service serves up.
So to avoid going all the way back to the server we have told the ThingsService to cache its results, which is a good start. But how are you supposed to update the changed thing in the cached data?
Is there a standard pattern for this kind of update with a ngResource service?
Preferably I wouldn't have to mess with the cache directly. I should not even care that it is cached or how. I just want $scope.things to reflect the posted changes changes.

Multi-steps register form in Angularjs

I'm building an app in which I've got a registration flow divided in 3 parts, Personal Info, Profile picture, and so on, each one in its invidual view.
So, which way do you think is the best way to persist data between views so in the final step I can show the whole data to the user and ask for confirmation.
Local Storage? $cacheFactory? I've really have no idea how to do it, any advice will be usefull!
Thanks in advance
cookie or local storage is available,but I think Angular way is recommended.
$rootScope: you can storage user data in $rootScope simply use
$rootScope.userData = {
'userName' : 'first',
'userId' : 'second'
};
to storage and read data in new view, remember to inject $rootScope into your controller
$broadcast: angular provide event $broadcast and $emit, so you can use $on to watch event and get your user data.and this is a demo http://jsfiddle.net/whitewolf/5JBA7/15/
route resolve? : if you send user data in every step to database,
you can use resolve in route config to load user data by $http.get().
add a service
app.factory("messageService", function($q){
return {
getMessage: function(){
return $q.when("Hello World!");
}};
});
in route config
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}}
})
in your controller
app.controller("newsController", function (message) {
$scope.message = message;
});
You are looking for an angular wizard
if you are using ui-router, you can easily do this with nested states
have a look
http://scotch.io/tutorials/javascript/angularjs-multi-step-form-using-ui-router
one form ( main state )
separate templates (nested states)
Note: specify controller for main state only and not for nested states
It will depend on the requirement.
If there is a requirement that the user can complete two steps and
come back later to complete the third step, then better go with local
persistence.
But if it's really not the case, you need not use any persistence
mechanism. Only thing is you need to properly map the models. And on the final submit, you can persist the complete input data.

Resources