AngularJS separate controller for details view? - angularjs

I'm currently developing an AngularJS application, and I've come across this one "issue" or I'd rather call it a "snag", that I'm not sure how to structure.
Would you create a different controller for a details views?
Let's say I'm creating a blog. The blog has some posts, and the first thing you'll see is the front page, showing the many posts that I've created. For this I would create a BlogController, just like this:
(function () {
"use strict";
angular.module("BlogApp")
.controller("BlogController", ["$rootScope", "$scope", "PostService", function ($rootScope, $scope, PostService) {
$scope.blogPosts = [];
PostService.getBlogPosts()
.then(function success(response){
// Success!
$scope.blogPosts = response.data;
}, function error(err){
// Error!
});
}])
})()
This is a very basic controller that just gets all my blog posts. Whenever I click a blog post, I'd like to navigate to another view, showing the details of this post. I could create a separate controller for this, but as my BlogController doesn't have much in it, I thought that I might as well just use this controller. I could do some actions based on the URL, like this:
(function () {
"use strict";
angular.module("BlogApp")
.controller("BlogController", ["$rootScope", "$scope", "PostService", "$location", function ($rootScope, $scope, PostService, $location) {
$scope.blogPosts = [];
var url = $location.url();
switch(url){
case "/blog":
PostService.getBlogPosts()
.then(function success(response){
// Success!
$scope.blogPosts = response.data;
}, function error(err){
// Error!
});
break;
case "/post-details":
// Do something specific for this post, if even anything needs to be done
break;
}
}])
})()
QUESTION
What is the most "correct" way of doing this? Perhaps "correct" is the wrong word, but I'd like to hear some argument for both methods. What would you do? Would you create a separate controller for the details?

Go with two different controller
After analyzing your case, I would suggest you to use a AngularJs component. I came up with this solution after going through the sample application tutorial developed by angular. This application has similar requirement like yours. Here, a click on the phone name would take you to a phone details page whereas in your case a click to blog will take you to blog details page. Clone the entire application using git clone --depth=16 https://github.com/angular/angular-phonecat.git
Even though you wish to go with your design, you should use different controllers as it becomes easy to refactor and test also as this suggests each time your controller is instantiated a new scope is created which can become tedious task to manage.

I think that you can use ng-include, you can mantain BlogController but implement some tamplate.html for a different situation about your blog post.
for example:
template1.html -> list of your blog post
template2.html -> detail of clicked post
inside of your html:
<div ng-include="template.url"></div>
inside your BlogController you can have similar situation:
$scope.templates =
[
{ name: 'template1.html', url: 'template1.html'},
{ name: 'template2.html', url: 'template2.html'}
];
$scope.template = $scope.templates[0];

Related

Resolve must contain all promises even from controller?

Probably it's just as easy as I think it is, but I cannot really find an answer to my question on the internet, so I hope you guys know the answer just by looking at a small piece of my code.
Problem: I'm using the UI router in Angular and it loads the template before all the data is loaded. So all input fields receive the correct values AFTER the template is already loaded. So the input fields are empty for a second or two....
I think my resolve is not as it should be:
So my ui-router code looks something like this (check the resolve object):
$stateProvider.state('teststate', {
url: '/test/',
templateUrl: 'app/page/template.html',
controller: 'testCtrl',
resolve: {
access: ["Access", function(Access) { return Access.isAuthenticated(); }],
UserProfile: 'UserProfile'
}
});
Now the controller contains the promise to get some data from an API url:
function TestCtrl($scope, $state, $stateParams, TestService) {
TestService.get($stateParams.id).then(function(response) {
$scope.data = response;
});
}
Now the service (which connects to the API) should return the promise to the Controller:
TestService.factory('TestService', ['Restangular', function(Restangular) {
var factory = {};
factory.get = function(id) {
return Restangular.one('api/test', id).get();
}
return factory;
}]);
Now, could the problem be, that because the TestService.get() (which connects to the API) within the Controller, gets executed NOT before the template is loaded, because it's not inside the resolve object? So the UI router doesn't resolve the call to the API? I'm just curious or I should move all methods which make API calls, to the resolve object of each stat inside the $stateProvider.
I could run a lot of tests, but if someone just directly knows the answer by just looking at this question, it helps me a lot.
Your assumptions are all correct.
If you resolve the TestService.get in routing config the data would be readily available to controller as an injectable resource
If you don't want your controller to run and your template to show before all your API calls are finished, you have to put all of them inside ui-routers resolve.
However, if API requests can take a little while it seems better UX to transition to the new page immediately and show some kind of loading indicator (e.g. block-ui) while your API call is running.

Angular JS - Cannot identify the suitable place to put a piece of code that will be used by different pages

I am making a page that is extracting some information from the server and showing it on the interface. I am using angular js for this. So I have made a controller that has $http.get() method which gets data from the server and then the data binding is used to bind data to the html page. I am using this controller...
mission_vision_mod.controller('mission_visionCtrl', ['$scope','$http', function($scope, $http) {
$scope.visiontext = "Here is the content of vision";
$scope.bkclr = ['bk-clr-one','bk-clr-two','bk-clr-three','bk-clr-four'];
$scope.progressbar = ['progress-bar-warning','progress-bar-danger','progress-bar-success','progress-bar-primary'];
$scope.missioncount = ['col-md-0','col-md-12','col-md-6','col-md-4','col-md-3','col-md-2.5','col-md-2'];
$http.get('m_id.json').success(function(data){
$scope.missions = data;
$scope.len = data.length;
});
}]);
Now i want to make a page that allows the users to edit this info, this page also requires the same above code (code inside the controller). I also have to make a different controller for the new page to send whatever data that has been edited to server.
How do i use the above code for both the pages while i have to make a new controller for the second one for editing purpose. I want to use the same code for both the controllers.
I would suggest moving that code to a Service, then inject and use that service in each of the controllers where you need this functionality.
Services are often the best place to add code that is shared between multiple controllers or if you need a mechanism to pass data betweeen controllers.
Hope this helps!
Service/factory/value are meant for this , please refer the below example hope you get a better idea .
var app = angular.module('myApp',[]);
//controller
app.controller('myCtrl',myCtrl);
//inject dependencies
myCtrl.$inject = ["$scope","httpService"];
function myCtrl($scope,httpFactory){
$scope.data = httpFactory.getData;
}
//factory : http factory
app.factory('httpFactory',httpFactory);
//inject dependencies
httpFactory.$inject = ["$http"];
function httpFactory($http){
var datas = [];
return {
getData:function(){
$http.get('url').success(function(data){
datas.push(data);
}).
error(function(){
console.log('error');
});
return datas
}
}
}

Ways of loading data into controller via service in AngularJS

I have a service that loads data using $http and returns a promise (simplified for brevity):
angular.module('myApp').factory('DataService', ['$http', function($http) {
function unwrapFriendList(data) {
...
return unwrappedFriendList;
}
return {
getFriendList: function() {
return $http.get('/api/friends').then(unwrapFriendList);
}
}
}]);
Here is a view that uses that data, after promise is resolved and result is stored in $scope.friends:
<div ng-repeat='friend in friends'>
{{friend.firstName}} {{friend.lastName}}
</div>
When it comes to loading that data into the controller, I've come across a couple of ways to do that.
Option 1: Controller that uses data loaded via ng-route resolve
angular.module('myApp').controller('FriendListCtrl', ['$scope', 'friendList', function($scope, friendList) {
$scope.friends = friendList;
}]);
Route section:
angular.module('myApp', ...).config(function($routeProvider) {
$routeProvider
.when('/friends', {
templateUrl: 'views/friends.html',
controller: 'FriendListCtrl',
resolve: {
friendList: ['DataService', function(DataService) {
return DataService.getFriendList();
}]
}
})
...
});
Option 2: Controller that triggers data loading by itself
angular.module('myApp').controller('FriendListCtrl', ['$scope', 'DataService', function($scope, DataService) {
DataService.getFriendList().then(function(friendList) {
$scope.friends = friendList;
});
}]);
Questions
Are there other commonly used ways of doing this? If so, please illustrate with a code example.
What are the limitations of each approach?
What are advantages of each approach?
Under what circumstances should I use each approach?
Unit testing
Option 1:
Using resolves makes mocking dependencies in controller unit tests very simple. In your first option:
$routeProvider
.when('/friends', {
templateUrl: 'views/friends.html',
controller: 'FriendListCtrl',
resolve: {
friendList: ['DataService', function(DataService) {
return DataService.getFriendList();
}]
}
})
angular.module('myApp')
.controller('FriendListCtrl', ['$scope', 'friendList',
function($scope, friendList) {
$scope.friends = friendList;
}]);
Since friendList is injected into the controller, mocking it in a test is as simple as passing in a plain object to the $controller service:
var friendListMock = [
// ...
];
$controller('FriendListCtrl', {
$scope: scope,
friendList: friendListMock
})
Option 2:
You can't do this with the second option, and will have to spy on/stub the DataService. Since the data data requests in the second option are immediately invoked on controller creation, testing will get very tangled once you start doing multiple, conditional, or dependent (more on that later) data requests.
View initialisation
Option 1:
Resolves prevent view initialisation until all resolves are fulfilled. This means that anything in the view expecting data (directives included) will have it immediately.
Option 2:
If data requests happen in the controller, the view will display, but will not have any data until the requests are fulfilled (which will be at some unknown point in the future). This is akin to a flash of unstyled content and can be jarring but can be worked around.
The real complications come when you have components in your view expecting data and are not provided with it, because they're still being retrieved. You then have to hack around this by forcing each of your components to wait or delay initialisation for some unknown amount of time, or have them $watch some arbitrary variable before initialising. Very messy.
Prefer resolves
While you can do initial data loading in controllers, resolves already do it in a much cleaner and more declarative way.
The default ngRoute resolver, however, lacks a few key features, the most notable being dependent resolves. What if you wanted to provide 2 pieces of data to your controller: a customer, and the details of their usual store? This is not easy with ngRoute:
resolve: {
customer: function($routeParams, CustomerService) {
return CustomerService.get($routeParams.customerId);
},
usualStore: function(StoreService) {
// can't access 'customer' object here, so can't get their usual store
var storeId = ...;
return StoreService.get(storeId);
}
}
You can hack around this by loading the usualStore from the controller after the customer is injected, but why bother when it can be done cleanly in ui-router with dependent resolves:
resolve: {
customer: function($stateParams, CustomerService) {
return CustomerService.get($stateParams.customerId);
},
usualStore: function(StoreService, customer) {
// this depends on the 'customer' resolve above
return StoreService.get(customer.usualStoreId);
}
}
Are there other commonly used ways of doing this?
Depends, If you have data that is on other domain and it can take time loading so you cant show the view until it get received so you will go for resolve one i.e first.
What are the limitations of each approach?
Limitation of using the first pattern the resolve one can be that the page won't display anything until all the data has loaded
Limitation of second one is that data may take longer to be recieved and your view will be like "{{}}" if you have not tackled it with css
What are advantages of each approach?
Advantage of first one is what i have said earlier that you will resolve the data and ensure it that it is present before view is rendered
Under what circumstances should I use each approach?
the resolve is very useful if we need to load some data loaded before the controller initialisation and rendering the view
And second one is when you dont have check ins and these loading problems expected and data is in you own hands !

Angular.JS share a single JSON object between controllers

I´m trying to code a CRUD app with Angular.JS, and I need your help to move on.
This is the scenario:
View 1 (index) gets JSONP data from a remote API and stores it.
View 2 (master) shows data filtered on a grid
View 3 (detail) shows an specific item selected on View 2
I did it already, but requesting the very same JSON object on each view, , but I think one only api call is enough.
I can´t figure out how to properly share this JSON object for all the controllers. I tried several tutorials on ngResource, $http, factories and services but still have not a clear path to go through.
How can I do this?
Any snippet or code sample you may share will be very useful to keep on tryin this thing...
Thanks in advance,
Ariel
You can implement a base controller to store common functionality that's shared between the controllers. I wrote a blog post about it recently, here's the code snippet showing how it works:
'use strict';
angular.module('Diary')
// base controller containing common functions for add/edit controllers
.controller('Diary.BaseAddEditController',
['$scope', 'DiaryService',
function ($scope, DiaryService) {
$scope.diaryEntry = {};
$scope.saveDiaryEntry = function () {
DiaryService.SaveDiaryEntry($scope.diaryEntry);
};
// add any other shared functionality here.
}])
.controller('Diary.AddDiaryController',
['$scope', '$controller',
function ($scope, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
}])
.controller('Diary.EditDiaryController',
['$scope', '$routeParams', 'DiaryService', '$controller',
function ($scope, $routeParams, DiaryService, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
DiaryService.GetDiaryEntry($routeParams.id).success(function (data) {
$scope.diaryEntry = data;
});
}]);
Using services to cache and share the data across controllers would be the way to go. Since services in angular are singleton, the same copy of data can be shared. A service such as
angular.module('myApp').factory('dataService', function($q, $resource) {
var items=[];
var service={};
service.getItems=function() {
var itemsDefer=$q.defer();
if(items.length >0)
itemsDefer.resolve(data);
else
{
$resource(url).query({},function(data) {
items=data;
itemsDefer.resolve(data)
});
}
return itemsDefer.promise;
}
return service;
});
Now in the controller you can inject the dataService and call the getItems method. This method returns a promise, which is either resolved using the cached data or by making remote request.
And the controller code would look something like
angular.module('myApp').controller('MyCtrl', function($scope,dataService) {
dataService.getItems().then(function(items) {
$scope.items=items;
}
});

is there an AngularJS version of jQuery's document ready

I am new to Angular and was able to create a basic page that create's a list of people with some checkboxes that allow you to choose them.
See this fiddle
The problems is that when I change the getAllPeople function to pull from a database
$http.post('angular.cfc?method=returnSerializeQuery').success(function(data) {
$scope.allPeople = data;
});
instead of building the array in the js the html loads to fast and loads with a blank list. if I then search the list shows up. I know that the $http call is too slow to keep up with the document load.
I have to use $timeout but I can't seem to get it to work an the dataset may sometimes take longer than other. If there is a Angular version of $(document).ready() I can't seem to find it.
I am also guessing that since this is my first fully functional page I am have not completely set it up right and that might have something to do with it.
If the idea is to delay the page rendering until data is fetched from the server, you have good answers (and examples) here and here.
The main idea:
function Ctrl($scope) {
$scope.data = {};
}
Ctrl.resolve = {
data: function($http) {
return $http({method: 'GET', url: '/path/to/some/data'});
}
};
var myApp = angular.module('app', [], function($routeProvider) {
$routeProvider.when('/', {
templateUrl: '/template.html',
controller: Ctrl,
resolve: Ctrl.resolve
});
});​
Also check this working example: http://jsfiddle.net/dTJ9N/54/
In your post().success callback function, after $scope.allPeople = data; add $scope.groupToPages(), since ng-repeat is watching pagedItems, not allPeople.

Resources