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

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.

Related

Switching the data, using the same Controller AngularJS

I was wondering how to change the data, but keep the same controller in my angular app. Basically I will have a list of activities (restaurants, parks etc...) when I click on one of these activities, The view will display all the restaurant, and same thing for the parks. I know how to do that, but I would need to create a park_ctrl and a restaurant_ctrl, and since the data will be formatted the exact same way. I just wanted to know if I could use only one controller and just change the data that it receives when I click on those buttons.
I hope my question makes sense.
logic around retrieving data should be the responsibility of services, so I guess you'd just call a different service in the different cases, from the same controller
I think it's not a really good idea, but opinion based.
You can make a function :
function($scope){
$scope.changePage = function (type) {
if(type==="park"){
$scope.parks = asynLoadFunctionToGetParks();
}else{
if(type === "restaurants"){
/* same as below */
}
}
};
}
And changing the type in your view with :
<button ng-click="changePage('parks')">Parks</button>
<button ng-click="changePage('restaurants')">Restaurants</button>
<div ng-if="type==='park'">
{{parks}}
</div>
<div ng-if="type==='restaurants'">
{{restaurants}}
</div>
I think the issue here is that most of the Angular examples available are of the "hello world" variety and so they show retrieving data directly from the Controller. The problem is that AngularJS out of the box doesn't really have a business logic layer itself, and I think most people who have added such a layer are too busy to be putting up examples.
The way I'd do this is to create a "master" service that can get all of the different data types either up front in the Run block or lazily as the user navigates the app, depending on your needs. Then I'd supply a reference to the applicable sub-collection in the route resolution (resolve property) or the isolate scope in the case of a directive.
Alternatively, the controller can ask for the data by calling masterService.getCollection($scope.collectionName) or something like that, but if you do that you run into the issue that masterService may not yet have that particular collection yet and then you have to clutter up your controller with all the promise resolution stuff as if it were a Controller's responsibility to handle that.
You could avoid that by binding to masterService.collections[$scope.collectionName] in the View, which would leave the Controller only exposing the collection on the $scope or controllerAs variable and the masterService still responsible for retrieving the data and making it available.
Yes you can. Just use different service and a common variable in the scope.
if (something) {
$scope.data = restaurantsService.get();
} else {
$scope.data = parksService.get();
}

Sharing data from multiple instances of a directive

I have a custom directive which is re-usable throughout the application. This directive uses ui-grid and I'm trying to determine the "angular way" of allowing access to the grid API from anywhere in the application.
If it was only a single instance, I'd use a service to share data across controllers:
var attachments = angular.module('attachments', ['ui.grid']);
// this would be accessible from any of my controllers
attachments.factory('Attachments', function() {
return {};
});
attachments.directive('attachments', function() {
return {
restrict: 'E',
templateUrl: 'attachments.html', // template has a ui-grid, among other elements
controller: ['$scope', 'Attachments', function($scope, Attachments) {
$scope.gridOptions = {
// ui-grid code here
onRegisterApi: function( gridApi ) {
Attachments.grid = gridApi;
}
};
}]
};
});
However, there could be multiple instances of the directive
For example, there might be a primary instance of this directive and one inside a modal, or one in a sidebar, one in a modal, etc.
I suppose I could add property namespaces to that service...
Attachments = {
libraryGrid: // ...
someModalGrid: // ...
}
etc...
I'd prefer to avoid making a service for each possible instance, i.e.:
attachments.factory('SomeModalAttachments', function() {
return {};
});
While it would work it feels inefficient. However, both choices are a lot better than digging into the modal scope and finding child scopes with the necessary API.
Is there any other method I haven't considered?
To me it depends on your usage model.
If you're going to have multiple of these and other bits of the application are going to access them, then that means one of a few things:
The access is actually initiated from the grid. So you have perhaps many list pages, and the currently active list page is the one you want to deal with. So I'd have the grid register with all the things it wants to talk to, and deregister when it closes again. The other things would all be services (singletons).
There are a specified number of these grids, and you talk to them by name - so you want to interact with the list-page-grid, or the modal-grid or whatever. So you have each grid register with somewhere central (maybe a service that everything else talks to).
The grids are subsidiary to something. So a page includes the grid directive, and then that page wants to talk to that grid. You could pass an object into the directive, then have the grid register itself on that object. So you call the directive with "myGridCommunicationObject = {}", and then the grid does "$scope.myGridCommunicationObject.gridApi = gridApi". This doesn't let other bits of the application talk to the grid, but if really you only want whatever created the grid to talk to it, then it works well.
You could broadcast. So if you don't really care which grid you talk to, you just want to talk to any grid that's currently visible (say you're resizing them or something) you could just broadcast an event to them, and all your grid directives could listen for that event. Taking this one step further, you could broadcast and include a grid id or name in the parameter, and then have each grid check whether it's me before taking action.
Having said all those options, there's something about what you're doing that has a bit of a code smell to it. Really, arbitrary bits of the application shouldn't want to talk directly to the grid Api, they should be communicating with methods on the controller that holds the grid. Perhaps some examples of what you want to do would help, but it feels to me like the model should be one of the grid registering to use other services (e.g. resize notifications), or of the controller that owns the grid interacting with the grid, and other things interacting with that controller.

Maintain the values in Angular Views

I am new to Angular.js and i am facing problem in maintaining the JSON feed values in the view.
I am using different routers and when i launch the app it loads the home.html which in turns calls the homeCtrl and make an HTTP call and binds the data using ng-repeat ( in home.html ). If user clicks on the list item to brings them to detail.html ( kind of detail page ).
Now the problem i face is , on the detail page when user tabs the back button - the app goes to home.html and the homeCtrl again hits the webservice and bind the whole data once again. Which i feel is unwanted as the JSON datas was already collected on the 1st time page load itself.
How can i preserve the old data when user move back forth between different views so i no need to hit same call over and over.
Thanks and sorry if its really basic stuff.
How can i preserve the old data when user move back forth between
different views so i no need to hit same call over and over.
If you use the $http service to make the requests use the cache: true option to use the default angular cache as explained in the docs: https://docs.angularjs.org/api/ng/service/$http#caching
Here an example:
$http.get(url, { cache: true}).then(...);
You can also define your custom cache object through the $cacheFactory service: https://docs.angularjs.org/api/ng/service/$cacheFactory
If you want some more complete with expiration, size limit and other cool stuff try angular-cache: https://github.com/jmdobry/angular-cache
Hi I think that you need is create a factory or service;so in this way the controllers shared data between them.
Here's an example

pass data between controllers

I'm stating to learn AngularJS, coming from a lot of different MV* frameworks.
I like the framework, however I'm having trouble with passing data between Controllers.
Suppose I have a screen with some input (input.html) and a controller, let's say InputCtrl.
There's a button on this view which takes you to another screen, let's say approve (approve.html) with a controller ApproveCtrl.
This ApproveCtrl needs data from the InputCtrl. This seems like a very common scenario in bigger applications.
In my previous MV* frameworks, this would be handled like (pseudo-code):
var self = this;
onClick = function() {
var approveCtrl = DI.resolve(ApproveCtrl);
approveCtrl.property1 = self.property1;
approveCtrl.property1 = self.property2;
self.router.show(approveCtrl);
}
It would work like Controller- first.
You create the controller first, having a chance to put it in the right state; afterwards the View gets created.
Now, in AngularJS, I'm handling this like:
var self = this;
onClick = function(){
self.$locationService.path('approve');
}
This works like View-first.
You say to which view / route to navigate, the Controller gets created by the framework.
I find it hard to control the state of the created Controller and pass data to it.
I've seen and tried following approaches, but all have it's own issues in my opinion:
Inject a shared service into InputCtrl & ApproveCtrl and put all data to be shared on this service
This looks like a dirty work-around; the state in the shared service becomes global state, while I just need it to pass data to the ApproveCtrl
The lifetime of this shared service is way longer than what I need it for - just to pass data to the ApproveCtrl
Pass the data in $routeParams
This gets quite messy when having the pass a lot of parameters
Use $scope events
Conceptually, this is not something I would use events for - I just need to pass data to the ApproveCtrl, nothing event-ish
This is quite cumbersome; I have to send an event to the parent first, that would then broadcast it to it's children
Am I missing something here? Am I creating too many small Controllers?
Am I trying to hold on to habits from other frameworks too much here?
In terms of structure AngularJS is more Modular than MVC one.
Classic MVC describes 3 simple layers which interact with each other in such way that Controller stitches Model with View (and Model shouldn't rather work with View directly or vice versa).
In Angular you can have multiple, some completely optional, entities which can interact between each other in multiple ways, for example:
That's why there are multiple ways of communicating your data between different entities. You can:
Send messages directly between controllers using difference between this and $scope
Send messages using events
Send messages using shared system (Note: same link as above, answer shows both techniques)
or
Send messages using AJAX backend
Send messages using external system (such as MQ)
...and a lot more. Due to its diversity Angular allows developer/designer to choose way they are most comfortable with and carry on. I recommend reading AngularJS Developer Guide where you can find blessed solutions to some common problems.
If your intent is to simply share data between two views, a service is probably the way to go. If you are interested in persisting to a data store, you may want to consider some sort of back-end service such as a REST API. Take a look at the $http service for this.
Even if XLII gave a complete response, I found this tutorial using a service. It's very interesting and a simple way for sharing data between controlers using the 2 ways binding property : https://egghead.io/lessons/angularjs-sharing-data-between-controllers
I still havn't used it for now.
Otherwise there is also this other way, based on events : http://www.objectpartners.com/2013/08/21/using-services-and-messages-to-share-data-between-controllers-in-angularjs/
If you wish to pass simple string data from one page (page1) to another page (page2), one solution is to use traditional url parameters. Invoke the page2 route url with parameter like "/page2/param1/param2". The controller of page2 will receive the parameters in "routeParams". You will be able to access parameteres as routeParams.param1 and routeParams.param2. The code below is adopted from: How to get the url parameters using angular js
Invoke the page2 route from page1's controller(js) or a url in its html with parameters as:
"/page2/param1/param2"
Page2 route:
$routeProvider.when('/page2/:param1/:param2', {
templateUrl: 'pages/page2.html',
controller: 'Page2Ctrl'
});
And the controller:
.controller('Page2Ctrl', ['$scope','$routeParams', function($scope, $routeParams) {
$scope.param1 = $routeParams.param1;
$scope.param2 = $routeParams.param2;
...
}]);
Now you can access the parameters (param1 and param2) values in your page2's html/template as well.

Angularjs scope not retaining value

Friends..
For my understanding of how routing works in Angular I have created a simple application. This application has only two pages:
1. The first page will display all rows of the employee table. Upon clicking on a particular row, second page will display a form with details of that employee.
The list that is displayed on the first page uses the following code:
<table>
<tr ng-repeat="employee in employees">
<td>{{employee.firstname}} - {{employee. address}}</td>
<td><span ng-click="getSingleEmployeeDetails(employee.id)">Edit</a></td>
</tr>
</table>
I am using the same controller for both these pages and this controller looks like below:
function EmployeeCtrl($scope,$http,Employee,$location,$routeParams) {
// Get all employee details
var data;
Employee.query().then(function(_data) {
$scope.employees = _data.data;
});
// Get Single Employee Details
$scope.getSingleEmployeeDetails = function(id) {
$scope.employee = scope.employees[id];
$location.path('/editemployee/' + id);
}
}
However the issue I am facing is that when the code gets routed to /editemployee/1
for some reason the $scope.employees looses its values.
In other words the form never gets populated with employee details.
What am I doing wrong here ?
This has to do with scoping. The employees are loaded into the EmployeeCtrl when it is instantiated. Once you perform a routing event in getSingleEmployeeDetails() that causes a different controller to load with a different $scope. A $scope that is separate from the $scope inside EmployeeCtrl. One easy way around this is to let EmployeeCtrl handle the functionality of loading/displaying all employees and a single employee without routing to a new controller. The pros here is that it makes it easier to share information, and you don't have to reload the single employee information when the user clicks on a single employee because you can share that information more easily. The con is that you don't get back button navigation to navigate between selections of single employees.
The other option is to let the SingleEmployeeCtrl reload the information when it navigates. The pro is you get back button access again, but the con is you load the information twice (once for loading the full list, and twice for loading the employee information again). This also allows the user to bookmark single employee records, but who bookmarks things anymore?
Others have already explained the fact that a new controller (and $scope) are created when you change routes. Also note that $scope.employees is populated asynchronously, when the promise is resolved. What is likely happening is that getSingleEmployeeDetails() is being called before the promise is resolved, so the employees array is empty.
To solve the problem, I suggest a different architecture.
You have two views/pages. Each view in Angular typically has its own controller. Models/data are typically stored in services, and an API to retrieve and manipulate those models/data is made available/public by the service. A controller just glues everything together: it injects the service(s) it needs, and then references only the models/data that the associated view needs.
So, even though your app is simple, I suggest the above approach: one service (which stores your employee objects), two controllers, two views. In particular, put the query() call into your service (so it will be called once, when the service is created) and store your data in the service. The service API should define functions/methods that return a promise that will eventually contain the desired data (list of employees, or just one). The controllers should use those methods to get a reference to the desired data.
See also Should services expose their asynchronicity? for an example of how to store the data in the service.

Resources