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.
Related
I'm working on a project which involves the usage of maps where it is make operations, like plotting marker or polylines.
The functionalities are working fine but I 'm facing difficulty on using multiple map service provider.
I want to perform same set operation using a open layer. For do that, I have to change in all the places, like controllers and views. Plus, I have a service which feed the data to the controller in required format.
But I'm not sure how to create an abstraction layer for different map service provider on the client side.
I have all the map related logic in the controller
Basically I'm looking for some guidelines and suggestion which will help to switch between multiplier service provider with minimum number of changes.
Follows my controller code:
taxiFleetControlPanel.controller("overviewController", function ($scope,$location,sensorService,appService,nodeService,settingService,$timeout,$rootScope,instanceService,assetApiService) {
//google maps
function initMap() {.....}
function addMarker(){.....}
function addpolyLines(){....}
//openlayer maps
function openlayerInitMap() {.....}
function openlayerAddMarker(){.....}
function openlayerAddpolyLines(){....}
if there is requriment to use openalyer map the i will comment initMap(); in the same controller
funtion init(){
initMap();
//openlayerInitMap()
}
init();
})
Is there a better way to do it?
there are so many other dependent functions as well I have not put to maintain simplicity.
I tried the follow idea:
Wrote two different controllers and changed the controller in the route but if the any change made in one controller i will literally copy paste the same set of methods to other i am not able to maintain a clean code
Note: The application can support multiple service provider like Google maps, openalyers and leaflet.
If I understand correctly, you want to extract out some logic so that it can be used everywhere in your application?
If so, there are two ways I know of that you could achieve this. First off, you could create a service that encapsulates the logic. That way you can inject it into each controller that you need to use it with and just call the methods that way.
Alternatively, you can declare the functions at $root level, that way it is accessible in any scope anywhere in the application.
Here's how you'd achieve this:
https://stackoverflow.com/a/15026440/5349719
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();
}
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.
There is chance that I might not be able to explain my problem properly. Let me try.
I am developing a single page application using angular. This app basically displays the episodes of an online novel series. There is a navigation bar, which has query menus (Like, latest episode, episode of a particular date, episodes with a particular tag, etc). For each of these queries, i want a separate url.
/latest - should display the latest episode
/tag/:tagname - should return all episodes with that tag.
For all these queries, the resultant view is the same (list of episodes). So I will be using the same partial for all routes.
My question is, Should I actually create a new controller for each query? like, LatestEpisodeController, TagController?
Is there anyway I can use the url to determine what the user wants and run that query from within the same controller?
Ofcourse you can use same controller in routing definition, the question is what is the purpose of that? It will be worse to debug it later, if you have a shared functionality it's better to turn it into a factory or service and then use in controllers.
But the answer is YES, you can use same controllers and implement different behaviour basing on i.e. $location.path()
yes you can use single controller for multiple routing..
you can create different functions in controller and in each function do the according job.
In my case I have created different html page for different url and registered same controller for the html pages and in the html page I have called controller method using ng-init in div portion.
You can use same controller and same views as you wish...
$location can help you to get current path or full url if you want and you can call your service depends on your path...
here I write a little example for you to get the idea
PLUNKER
I'm using UI-Router to have some "menus" in my application.
$stateProvider.state("list"
url: "/Focales"
templateUrl: "/demo/focals.html"
controller: FocalCtrl)
$stateProvider.state("collection"
url: "/Collections"
templateUrl: "/demo/collections.html"
controller: CollectionCtrl)
In my CollectionCtrl, I trigger a processing done on server and just waiting to display the information like this (CoffeeScript)
Progress = $resource('/collections/getProgress')
$scope.getProgress = () ->
prg = Progress.get {}, ->
$scope.currentProgress = prg
$scope.getProgress() if prg.end isnt true
My issue : When the user moves to Focal and goes back to CollectionCtrl, I have a new instance of CollectionCtrl. So as far as I understand, the $scope.getProgress code still receives data but for the PREVIOUS CollectionCtrl (so the display is not updated...)
Is it possible to get the previous Controller rather than a new "instance" of the CollectionCtrl ? Why is a new CollectionCtrl created ?
What's the best approach: So I'm tempted to have a state data to store the current $scope so I could do $state.currentScope = prg rather than $scope.currentProgress = prg.
Is it a good approach ?
Thanks !
The best approach to take here is to define a service which will communicate with the server and possibly cache the results. This service can be injected in the Controllers and the controllers can ask it for the progress, probably using $watch, though providing the service with a callback may also suffice, and update the UI based on that.
Controllers should be ephemeral and one should not rely on them being singleton-ish objects in Angular. Importantly, they should not contain any state apart from that needed by the template they are supporting. The are created again when the route becomes active so that we can write comparatively simpler code, initializing the $scope once and rest assured that it will contains as fresh information (w.r.t the route) for the template as possible. This also avoids memory leaks if a controller is kept around for each route like /app/:object_id and the user switches routes often.