How to get the active status of nested Angular UI Bootstrap tabs - angularjs

In my code I'm using a service (ngResource) to get data dynamically from the server API to create a series of nested tabs.
My code is loosely like this:
var AdminMenu = angular.module('AdminMenu', ['ngResource', 'ui.bootstrap', 'ngGrid']);
AdminMenu.factory('myServices', ['$resource', function($resource) {
return $resource('/api/categories?sort=createdAt asc');
}
and my controller
$scope.categories = myServices.categories.query();
On my template (jade) I have
tabset
if(categories)
tab(ng-repeat="category in categories", heading="{{category.name}}", active="category.active", disabled="category.disabled")
tabset
tab(ng-model="subCats", ng-repeat="subcat in subCats", heading="{{subcat.name}}", active="tab.active") {{subcat.content}}
And that works ok, however I need to get the status (active and heading) of the second and third(not created yet) generation tabs. I can get it from the first generation as Angular seems to automatically add bits to each object of the first generation like active:true, some methods, etc. The same doesn't seem to apply to the subsequent generations.
Interestingly, the tabs are not declared in my controller (like $scope.tabs = obj for instance), they are being added dynamically.
How can I access the status and heading (all details, really) of each subsequent generation of tabs?
I've tried assign the second and third generation in my controller like so: $scope.tabs2 = $scope.categories.subcategories but that didn't work. I suspect it was because the data is being collected dynamically, but I'm not sure. Since I can't assign the tabs in my controller, I can't simply console $scope.tabs2.active to find out which tab is active.
Any suggestions?

Related

Retain Tab number in Navigation AngularJs

So I am relatively new to AngularJs and I am trying to figure out the best way for a tabController to remember what tab was previously clicked when switching to a new controller. So the situation would be I have 3 tabs. I click on tab 3 and then I click on something inside of it bringing me to a new controller and HTML template... What is the best way if I hit a "back" Button I created in that controller to remember exactly the state of it being the 3 tab.
I tried using $rootScope and then in each controller setting the tab number and setting the tabcontroller = $rootScope... but that was chaotic and too repetitive, and its not the right way.
This is not about $windoe.back(), this refers to coming up with a way that no matter where the navigation is the tab number is retained.
You can use a factory for this. In angular, a factory is a singleton, meaning only one instance of it exists for the whole project. Thus, by making something with it in one controller (and saving what you've did), you can access your changes in another controller.
angular.module('awesomeApp')
.factory('tabHistoryFactory', function () {
var tabHistory = {
setPrevTab: function(tab) { tabHistory.prevTab = tab; },
getPrevTab: function() { return tabHistory.prevTab; }
};
return tabHistory;
});
Then, in your first controller you'll have to inject this factory and before changing to another tab, just save the tab you're on using tabHistoryFactory.setPrevTab(tab). Then, in your second controller, you can access your previous tab by using tabHistoryFactory.getPrevTab(). Similarly, you can customize the behavior of your tab history by implementing other functions alongside those two.
Good luck!

AngularJS, single controller or multiple controllers for ng-included views?

I have a dashboard page. Say it is Student dashboard and it has lots of details about the student like his personal details, course details, project details and etc. Inside student.html personal-details.html, course-details.html,project-details.html are included using ng-include.
Student can view those details as well as they can edit it. For this functionality, I have written a single controller StudentController as follows,
(function(){
"use strict";
angular.module('myApp')
.controller('StudentController',StudentController);
StudentController.$inject=['StudentService'];
function StudentController(StudentService){
var vm = this;
vm.studentData = null;
vm.editPersonalDetails = editPersonalDetails();
vm.editCourseDetails = editCourseDetails();
activate();
function activate(){
StudentService.getStudent().then(queryStudentCompleted);
}
function queryStudentCompleted(res){
vm.studentData = res.data;
}
function editPersonalDetails(){
StudentService.editPersonalDetails(vm.studentData);
}
function editCourseDetails(){
StudentService.editCourseDetails(vm.studentData);
}
}
In my real application there are lots of panels to be shown and everything is editable.
1)Should I use different controller for different ng-included pages or single controller is enough(think that more than 7 htmls are included and every panel is editable)? if yes how do the child controllers(say personalDetailsController, courseDetailsController which have edit methods in it) get access to the vm.studentData which is received in the studentController? In Page I can directly show it using ControllerAs syntax, but when I edit it I need to send the edited values to the child controller..
2)Currently studentData has every details that should be shown in the page. Should I keep that in that way or I need to split the data as well into smaller chunks?

Angular CRUD, update view when backend/database changes ($resource and REST)

I am currently making an application in angular which does this:
(On page load) Make an api call in angular controller (to symfony2 end point) to get: items.
$scope.items = ItemsService.query(function(data){
$scope.loading = false;
}, function(err){
$scope.loading = false;
});
items is an array containing many item objects.
Each item contains parameters e.g. item.param1 item.param2.
I have built it in a similar way to this tutorial:
http://www.sitepoint.com/creating-crud-app-minutes-angulars-resource/
i.e. The angular controller calls a service which calls the (symfony2) backend api endpoint.
The endpoint passes back items which is gets from a database. Items are then put into the view using ng-repeat (item in items).
This all works fine.
Now, I have a button (in the ng-repeat) which effectively causes a PUT request to be made to (another symfony2 endpoint), thus updating item.param1in the database. This also happens in the tutorial I linked to.
The problem is that (in my application and in the tutorial) I have to again make an api call which updates ALL the items, in order to see the change.
I want to just update the data in the view (immediately) for one object without having to fetch them all again.
i.e. something like:
$scope.items[4] = Items.get({id: item.id}, function(){});
Except the application array key isn't known so I cant do that.
(so something like: $scope.items.findTheOriginalItem(item) = Items.get({id: item.id}, function(){});.
Another possible solution (which seems like it may be the best?). I have looked here:
http://teropa.info/blog/2014/01/26/the-three-watch-depths-of-angularjs.html
And tried doing the equality $watch: $scope.$watch(…, …, true);. Thus using watch to see when the item sub-array is updated. This doesn't seem to update the data in the view though (even though it is updating in the database).
Any advice on the best way of doing this (and how to do it) would be great! Thanks!
Essentially the button click should use ng-click to execute a function and pass the item to that function. Example:
...ng-repeat="item in items"...
<button type="button" ng-click="updateItem(item)">Update</button
...
Then in the function you have the exact item that you want to update. If you are using $resources, it would be something like:
$scope.updateItem = function(item) { item.$update(...); };
Unless I didn't understand you

(How) can a controller be active when its view is not visible?

If I understand correctly, a controller is generated when its view becomes active?
Meaning that if the view is not visible, the controller doesn't exist/isn't active?
Is there any way that I can make the controller active "in the background"?
I have an app which has two main tabs, one for vehicle tracking & the other for something else. Each of those tabs has several sub-tabs (suing UI-Router); in the case of the vehicle tracking, I have one tab for a google map & another to show a grid with street addresses.
Here's a pic of the desktop app which I am trying to re-implement in Angular:
Now, I am an Angular beginner, so maybe my design idea is flawed, but:
Both the map and the vehicle locations tab use the identical data pulled from a server, which is a list of GPS lat/long & timestamp.
It seemed to me that it would be wrong to have each of those tabs (controllers) fetching the same data, duplicating code.
So, I wanted to have a controller for the "Vehicle locations" tab, which would get the data and feed it to a service which the two map/Vehicle locations sub-tabs (controllers) could $watch.
Even if I were not looking at that "parent view" ("Vehicle locations", but at another ("Vehicle reports" or "Employee reports"), I would this data downloading & view updating to continue, so that when the user clicks the "Vehicle locations" tab, the information is already there & the user doesn't have to wait while the app goes to the server to fetch data & draw its view.
Question: can Angular work that way, or am I asking it to do something which is un-angular-ish? If so, what is the correct way to implement this? Should I duplicate the $http.get code and should I have the controllers fetching & updating their own data on $scope.$on('$viewContentLoaded'?
If you need a central point of access for data in your AngularJS app, the best way to achieve it is to encapsulate it in a provider (either a service or a factory). Fetch the data in your provider and implement getters, so that you can inject it in your controllers and ask for the data without duplicating code and requests.
yourApp.service('serviceName', [ '$http',
function($http) {
this.getData = function() {
return $http.get(...);
}
}
]);
yourApp.controller('controllerA', ['serviceName',
function(serviceName) {
serviceName.getData().then(function(data){
$scope.data = data;
});
}
]);
//the same for other controllers
There are some points you can consider for this app:
You can create a factory for data you intend to share and reuse. So maybe create a http factory to get the data from the server and then inject it to whichever controllers you need to. Also you can resolve incoming data with "resolved" in angularjs (an option in routes) to make sure it's available before the page renders.
If you are passing in data within controller to http.get then, inject the factory and use promises with the http method.

AngularJS register controller once

That's what I'm doing. There is application with pages and different controls that may be put on pages by site admin/editor. All pages share one ng-app defined on master page. All controls are supplied with .js files with angular controllers. Let's suppose that I have an image gallery block:
<div ng-controller='imageGalleryCtrl'>
do something amazing here
</div>
<script src='imageGallery.js'></script>
Inside script there is a simple controller registration like:
angular.module('myApp').controller('imageGalleryCtrl', ... );
So. If I have 10 image galleries, I'll execute controller registration 10 times. It looks like this will work, but hell - I don't want it to be so =)
For now I just have all controls' scripts registration on a master page, but I don't like it as well, because if there is no image gallery on a page, I don't want it's script be downloaded during page load.
The question is - is there any proper way to understand if controller have been registered in a module already and thus prevent it from re-registering?
---------------
Well, though I've found no perfect solution, I must admit that the whole idea isn't very good and I won't think about it before my site will grow too big to assemble whole angular app on master page.
You should declare your controller but once. Instead of having one controller per gallery, have your single controller handle all image galleries. The controller should make a request to the REST backend to fetch the images of the desired gallery.
I see that instead of ng-view, you're using the ng-controller directive, indicating that probably you're not using Angular's routing. Try switching to using routes.
Have a look at Angular.js routing tutorial. It shows you how to use the ngRoute module. Then, in the next chapter, the use of $routeParams is described. Via the $routeParams service, you can easily say which gallery should be displayed by providing its ID in the URL; only one controller will be necessary for all your galleries.
If you really must check whether a given controller has been declared, you can iterate through the already declared controllers (and services... and pretty much everything else) by checking the array angular.module("myApp")._invokeQueue. The code would probably look something like this (not tested!):
var isRegistered = function(controllerName)
{
var i, j, queue = angular.module("myApp")._invokeQueue;
for (i = 0, j = queue.length; i < j; ++i) {
if (
queue[i][0] === "$controllerProvider"
&& queue[i][1] === "register"
&& queue[i][2][0] === controllerName
) {
return true;
}
}
return false;
};
Bear in mind however that while this may (or may not) work, it's far from being the correct thing to do. It's touching Angular's internal data that's not meant to be used in your code.

Resources