I'm currently using one controller for my web app. Data is loaded from a JSON file and shown to the user via ng-repeat. When a user makes a selection, only data for the user's selection is in the scope.
I'd like to be able to keep the same scope data and use it across different web pages (or states using UI-Router).
I've looked into using ui-router but it seems like the controller would be refreshed with every state change.
I'd prefer to use ui-router due to design requirements.
Part of my controller code:
(function() {
"use strict";
angular
.module("parkit", ["ngMap"])
.controller("parkitController", function($scope, $rootScope, $http,
parkitFactory, NgMap) {
parkitFactory.getSpots().then(function(spots) {
$scope.spots = spots.data;
});
$scope.showSpot = function(spot) {
$scope.spot = spot;
}
});
})();
Factory code for loading JSON data:
(function() {
"use strict";
angular.module("parkit")
.factory("parkitFactory", function($http) {
function getSpots() {
return $http.get('/data/spots.json');
}
return {
getSpots: getSpots
}
});
})();
As it has been answered before, you can use a factory or service to keep track of the selected item(s). This would store the selected values in the instance of the service/factory and therefore would be lost if someone refreshes the page.
A more resilient, and in my opinion beautiful solution, would be to add the selected item(s) as state parameter in ui-router. Using this method, you will also be able to deep-link to certain selected states and if someone refreshes the page, the same items would still be selected, as you would add your state parameters in the url.
See URL Parameters in the documentation: https://github.com/angular-ui/ui-router/wiki/URL-Routing
You may probably create a new property in the factory function to keep track of the selected item.Set the property when user does a selection. Get the property in other components whereever you need to use the data.
use $rootScope instead of $scope to save your data, That will allow you to use it anywhere in controllers of same domain.
example: $rootScope.yourData = yourData; and then you can assign $rootScope.yourData to any controller in same domain.
I have a simple Angular JS scenario. I show a list of experts in one view, and that list contains actions to edit an expert, which would be done in another view. These views are coming from server and are not in one file. They don't load together.
<!-- experts.html -->
<div ng-controller='expertController'>
<!-- showing the list of experts here -->
</div>
And in another file on the server I have:
<!-- expert.html -->
<div ng-controller='expertController'>
<!-- a form to edit/add an expert -->
</div>
And my controller is:
app.controller('expertController', function ($scope) {
$scope.getExperts = function () {
_.get('/expert/getexperts/', null, function (data) {
$scope.$apply(function () {
$scope.experts = data;
});
});
};
$scope.editExpert = function (index) {
_.get('/expert/getexpert/', { id: $scope.experts[index].Id }, function (data) {
$scope.$apply(function () {
$scope.expert = data;
});
});
};
});
However, no data would be shown in the edit form. I inspected scopes using Batarang, but expert object won't be shown.
The problem seems to be that I use one controller for two (and optionally more than two) views. However, as I've read some other questions on SO, it seems that the best practice is to have one controller per HTML view.
However, I think it's more coherent and consistent to have one expertController to do all CRUD operations, alongside other actions related to expert. I think it's a little ugly to have expertEditController for edit form and expertListController for the list HTML.
What is the best practice here? How can I have a coherent and semantically named controller, and at the same time support many views. In any MVC framework that I've seen till now, it's something routine to support many views via one controller.
Of course you can use many views with the same controller.
For example: for editing and adding new items.
But you have to remember that each time the view is loaded the controller is initialized and fresh, new $scope is injected.
In your case it would be better to just use two separate controllers (one for experts and one for expert) and use -- optionally -- some service to communicate between those and maybe provide some caching mechanism.
UPDATE In case you want to provide some common/shared functionality between controllers the best way to go is to create and then inject a separate service. It keeps the code DRY.
Here's an example project (not created by me) that illustrates how to use a single controller with two views: one for showing a list of items and one for editing or adding a single item: http://embed.plnkr.co/jAK31F/
The basic principle used in the controller is to check whether an id of a single item has been passed and run the applicable logic for that situation:
controllers.controller('MainController', function( $scope, $location, $routeParams, ItemService) {
if ($routeParams.itemid) {
// do stuff for single item
if ($routeParams.itemid == 0) {
// do stuff for adding item
} else {
// do stuff for editing item
}
} else {
// do stuff for listing multiple items
}
});
The different views are configured in the routes like so:
app.config(['$routeProvider', function ($routeProvider) {
// routes
$routeProvider.
when('/', {
templateUrl: 'item_list.html',
controller: 'MainController'
}).
when('/:itemid', {
templateUrl: 'item_single.html',
controller: 'MainController'
});
}]);
So an url like baseurl/ will open the list view, and an url like baseurl/1 will open the detail view of the item with id 1.
With regard to this being a best practice or not, for most situations I don't think this method offers an advantage over using two controllers: one for the list, and one for a single item. In my experience a controller to view multiple items has different concerns than one for editing a single item. Any shared logic can be placed in a service as suggested in earlier answers.
Actually when we use same controller for other view, it will reloads that controller whenever the view loads. At that moment scope will lose it's data.To avoid this i used $rootScope instead of $scope. I don't know whether this approach is correct or not but it works.
$rootScope.expert instead of $scope.expert.
Started using Angular last week, read/watched many tutorials and I'm currently trying to build a newsfeed type application.
Here's the skinny: I have a service that gets data from the server. On the newsfeed itself I have two controllers: one that has the entire newsfeed in its scope and another that has an instance for each newsfeed article. If the user clicks an icon on an individual post it should call a service that has been injected into both controllers and then broadcasts a message that the main controller picks up. The main controller then updates a variable in a filter, filtering the newsfeed content based on the user's selection.
Here's the problem: Everything works fine except that the main controller doesn't update the bound variable in the HTML. I have read close to every SO article on two-way binding within an ng-repeat and the related struggles, but in my case the bound variable falls outside an ng-repeat, hence why I'm posting.
The code:
services.factory('filterService', function() {
var filterService = {};
filterService.filterKey = '';
filterService.getFilter = function() {
return filterService.filterKey;
};
filterService.setFilter = function(name) {
filterService.filterKey = name;
$rootScope.$broadcast('changeFilter');
};
return filterService;
});
app.controller('CommentCtrl', function($scope, $timeout, $http, filterService) {
$scope.setSearchParam = function(param) {
alert('clicked: ' + param)
filterService.setFilter(param);
}
app.controller('FeedCtrl', function($scope, articles, filterService, $timeout) {
$scope.articles = articles;
$scope.model = {
value: ''
};
$scope.$on('changeFilter', function() {
console.log(filterService.filterKey);
$scope.model.value = filterService.filterKey
}
});
});
<div class="articles">
<div class="articleStub" ng-repeat="article in articles|filter:model.value">
<div ng-controller="CommentCtrl">
<div class="{{article.sort}}">
<div class="leftBlock">
<a href="#" ng-click="setSearchParam(article.sort)">
<div class="typeIcon">
<i ng-class="{'icon-comments':article.question, 'icon-star':article.create, 'icon-ok-sign':article.notype}"></i>
</div>
</a>
Note: the FeedCtrl controller is called in the app.config $routeprovider function thing whatever its called
Edited to add: the alert and console checks both work, so I'm assuming the issue is not in the filterService or CommentCtrl.
Here's the Plnkr: http://plnkr.co/edit/bTit7m9b04ADwkzWHv88?p=preview
I'm adding another answer as the other is still valid, but is not the only problem!
Having looked at your code, your problems were two fold:
You had a link to href="#"
This was causing the route code to be re-run, and it was creating a new instance of the controller on the same page, but using a different scope. The way I found this out was by adding the debug line: console.log("running controller init code for $scope.$id:" + $scope.$id); into script.js under the line that blanks the model.value. You'll notice it runs on every click, and the $id of the scope is different every time. I don't fully understand what was happening after that, but having two of the same controller looking after the same bit of the page can't be a good thing!
So, with that in mind, I set href="". This ruins the rendering of the button a bit, but it does cure the problem of multiple controllers being instantiated. However, this doesn't fix the problem... what's the other issue?
angular.element.bind('click', ....) is running 'outside the angular world'
This one is a bit more complicated, but basically for angular data-bindings to work, angular needs to know when the scope gets changed. Most of the time it's handled automagically by angular functions (e.g. inside controllers, inside ng-* directives, etc.), but in some cases, when events are triggered from the browser (e.g. XHR, clicks, touches, etc.), you have to tell angular something has changed. You can do this with $scope.$apply(). There are a few good articles on the subject so I'd recommend a bit of reading (try here to begin with).
There are two solutions to this - one is to use the ng-click directive which wraps the native click event with $scope.$apply (and has the added advantage that your markup is more semantic), or the other is to do it yourself. To minimise the changes to your code, I just wrapped your click code in scope.$apply for you:
element.bind('click', function() {
// tell angular that it needs to 'digest' the changes you're about to make.
scope.$apply(function(){
var param = scope.article.sort;
filterService.setFilter(param);
})
});
Here's a working version of your code: http://plnkr.co/edit/X1AK0Bc4NZyChrJEknkN?p=preview
Note I also set up a filter on the list. You could easily ad a button to clear it that is hidden when there's no filter set:
<button ng-click="model.value=''" ng-show="model.value">Clear filter</button>
Hope this helps :)
I actually think the problem is not that your model.value isn't getting updated - all that code looks fine.
I think the problem lies in your filter.
<div class="articleStub" ng-repeat="article in articles|filter:model.value">
This filter will match any object with any field that contains model.value. What you actually want to do is the following:
<div class="articleStub"
ng-repeat="article in articles|filter:{sort: model.value}:true">
To specify that you only want to match against the sort property of each article. The final true parameter means that it'll only allow strict matches as well, so ed wouldn't match edward.
Note that | filter:{sort: model.value}:true is an angular expression, the :s are like JavaScript commas. If you were to imagine it in JavaScript it would be more like: |('filter',{sort:model.value}, true) where | is a special 'inject a filter here' function..
EDIT:
I'm finding it hard to debug your example without having the working code in front of me. If you can make it into a plunker I can help more, but in the meantime, I think you should try to make your code less complicated by using a different approach.
I have created a plunker that shows an easy way to filter a list by the item that you click. I've used very little code so hopefully it's quite easy to understand?
I would also recommend making your feed items into a directive. The directives can have their own controller so it would prevent you having to do the rather ugly repeating of a ng-controller.
I am building an AngularJS application that has a sidebar and a main content area. Both are populated by separate invocations of the $http service to retrieve some JSON data. The stuff in the sidebar is basically a table of contents and the intent is that clicking on one of the sidebar items will cause the main area to be updated.
My initial stab at this involved putting the sidebar and main area into one partial and then associating it with a controller that does both retrievals. The application has a route that associates the controller to a URL, and the links in the sidebar access these URL with the appropriate parameter that will cause the desired content to appear in the main area. All this works, but it does cause the whole page to refresh. The "partial" really is a "total".
What I'd like to do is somehow cause a click on the sidebar links to trigger a refresh of the main content area only. One thought is to somehow split it into two controller/partial pairs and figure out a way to cause sidebar clicks to request an update. I'm not sure about the mechanics of doing this, though. Another approach is to keep the stuff in one controller and use some kind of shared state that would trigger this update. I attempted to do this by setting an ng-click directive on the links, but this did not update a scope variable, as I had hoped.
Is there a recommended way of structuring an application to achieve this kind of AJAX-driven partial page update? It seems like a fairly common case, but I haven't mastered enough of AngularJS to get a solution.
This is what I'm doing:
I have 2 services, 1 for the sidemenu and the other for the main content. They are both injected into the controller.
To handle cross service calls I use $broadcast to send events.
Works really well and is very clean code.
additional info on using services based on comment
For the sidemenu i have a shared menu service, that way all controllers can use the same menu.
The maincontent service doesnt have to be shared, but i use them because services don't loose their data when the controller goes out of scope. If I didn't the controller would have to use some other mechanism to repopulate its data. For me a service handles it without having to code anything
To show the different views i use the following main layout html
<div >
<!-- left menu -->
<div id="left" ng-include src="menu.view" ></div>
<!-- main content -->
<div id="main" ng-include src="maincontent.view"></div>
</div>
the controller
function myControllerCtrl($scope, $rootScope, menuService, maincontentService) {
$scope.menu = menuService;
$scope.maincontent = mainContentService
}
the menu service
app.factory('menuService', ['$rootScope', function ($rootScope) {
var service = {
view: 'leftmenuview.html',
MenuItemClicked: function (data) {
$rootScope.$broadcast('menuitemclicked', data);
}
};
return service;
}]);
the main content service
app.factory('maincontentService', ['$rootScope', function ($rootScope) {
var service = {
view: 'maincontentview.html',
MenuItemClicked: function(data){
//handle updating of model based on data here
}
};
$rootScope.$on('menuitemclicked', function (event, data) { service.MenuItemClicked(data) });
return service;
}]);
I understand AngularJS runs through some code twice, sometimes even more, like $watch events, constantly checking model states etc.
However my code:
function MyController($scope, User, local) {
var $scope.User = local.get(); // Get locally save user data
User.get({ id: $scope.User._id.$oid }, function(user) {
$scope.User = new User(user);
local.save($scope.User);
});
//...
Is executed twice, inserting 2 records into my DB. I'm clearly still learning as I've been banging my head against this for ages!
The app router specified navigation to MyController like so:
$routeProvider.when('/',
{ templateUrl: 'pages/home.html',
controller: MyController });
But I also had this in home.html:
<div data-ng-controller="MyController">
This digested the controller twice. Removing the data-ng-controller attribute from the HTML resolved the issue. Alternatively, the controller: property could have been removed from the routing directive.
This problem also appears when using tabbed navigation. For example, app.js might contain:
.state('tab.reports', {
url: '/reports',
views: {
'tab-reports': {
templateUrl: 'templates/tab-reports.html',
controller: 'ReportsCtrl'
}
}
})
The corresponding reports tab HTML might resemble:
<ion-view view-title="Reports">
<ion-content ng-controller="ReportsCtrl">
This will also result in running the controller twice.
AngularJS docs - ngController
Note that you can also attach controllers to the DOM by declaring it
in a route definition via the $route service. A common mistake is to
declare the controller again using ng-controller in the template
itself. This will cause the controller to be attached and executed
twice.
When you use ngRoute with the ng-view directive, the controller gets attached to that dom element by default (or ui-view if you use ui-router). So you will not need to attach it again in the template.
I just went through this, but the issue was different from the accepted answer. I'm really leaving this here for my future self, to include the steps I went through to fix it.
Remove redundant controller declarations
Check trailing slashes in routes
Check for ng-ifs
Check for any unnecessary wrapping ng-view calls (I accidentally had left in an ng-view that was wrapping my actual ng-view. This resulted in three calls to my controllers.)
If you are on Rails, you should remove the turbolinks gem from your application.js file. I wasted a whole day to discover that. Found answer here.
Initializing the app twice with ng-app and with bootstrap. Combating AngularJS executing controller twice
When using $compile on whole element in 'link'-function of directive that also has its own controller defined and uses callbacks of this controller in template via ng-click etc. Found answer here.
Just want to add one more case when controller can init twice (this is actual for angular.js 1.3.1):
<div ng-if="loading">Loading...</div>
<div ng-if="!loading">
<div ng-view></div>
</div>
In this case $route.current will be already set when ng-view will init. That cause double initialization.
To fix it just change ng-if to ng-show/ng-hide and all will work well.
Would like to add for reference:
Double controller code execution can also be caused by referencing the controller in a directive that also runs on the page.
e.g.
return {
restrict: 'A',
controller: 'myController',
link: function ($scope) { ....
When you also have ng-controller="myController" in your HTML
When using angular-ui-router with Angular 1.3+, there was an issue about Rendering views twice on route transition. This resulted in executing controllers twice, too. None of the proposed solutions worked for me.
However, updating angular-ui-router from 0.2.11 to 0.2.13 solved problem for me.
I tore my app and all its dependencies to bits over this issue (details here: AngularJS app initiating twice (tried the usual solutions..))
And in the end, it was all Batarang Chrome plugin's fault.
Resolution in this answer:
I'd strongly recommend the first thing on anyone's list is to disable it per the post before altering code.
If you know your controller is unintentionally executing more than once, try a search through your files for the name of the offending controller, ex: search: MyController through all files. Likely it got copy-pasted in some other html/js file and you forgot to change it when you got to developing or using those partials/controllers. Source: I made this mistake
I had the same problem, in a simple app (with no routing and a simple ng-controller reference) and my controller's constructor did run twice. Finally, I found out that my problem was the following declaration to auto-bootstrap my AngularJS application in my Razor view
<html ng-app="mTest1">
I have also manually bootstrapped it using angular.bootstrap i.e.
angular.bootstrap(document, [this.app.name]);
so removing one of them, it worked for me.
In some cases your directive runs twice when you simply not correct close you directive like this:
<my-directive>Some content<my-directive>
This will run your directive twice.
Also there is another often case when your directive runs twice:
make sure you are not including your directive in your index.html TWICE!
Been scratching my head over this problem with AngularJS 1.4 rc build, then realised none of the above answers was applicable since it was originated from the new router library for Angular 1.4 and Angular 2 at the time of this writing. Therefore, I am dropping a note here for anyone who might be using the new Angular route library.
Basically if a html page contains a ng-viewport directive for loading parts of your app, by clicking on a hyperlink specified in with ng-link would cause the target controller of the associated component to be loaded twice. The subtle difference is that, if the browser has already loaded the target controller, by re-clicking the same hyperlink would only invoke the controller once.
Haven't found a viable workaround yet, though I believe this behaviour is consistent with the observation raised by shaunxu, and hopefully this issue would be resolved in the future build of new route library and along with AngularJS 1.4 releases.
In my case, I found two views using the same controller.
$stateProvider.state('app', {
url: '',
views: {
"viewOne#app": {
controller: 'CtrlOne as CtrlOne',
templateUrl: 'main/one.tpl.html'
},
"viewTwo#app": {
controller: 'CtrlOne as CtrlOne',
templateUrl: 'main/two.tpl.html'
}
}
});
The problem I am encountering might be tangential, but since googling brought me to this question, this might be appropriate. The problem rears its ugly head for me when using UI Router, but only when I attempt to refresh the page with the browser refresh button. The app uses UI Router with a parent abstract state, and then child states off the parent. On the app run() function, there is a $state.go('...child-state...') command. The parent state uses a resolve, and at first I thought perhaps a child controller is executing twice.
Everything is fine before the URL has had the hash appended.
www.someoldwebaddress.org
Then once the url has been modified by UI Router,
www.someoldwebaddress.org#/childstate
...and then when I refresh the page with the browser refresh button, the $stateChangeStart fires twice, and each time points to the childstate.
The resolve on the parent state is what is firing twice.
Perhaps this is a kludge; regardless, this does appear to eliminate the problem for me: in the area of code where $stateProvider is first invoked, first check to see if the window.location.hash is an empty string. If it is, all is good; if it is not, then set the window.location.hash to an empty string. Then it seems the $state only tries to go somewhere once rather than twice.
Also, if you do not want to rely on the app's default run and state.go(...), you can try to capture the hash value and use the hash value to determine the child state you were on just before page refresh, and add a condition to the area in your code where you set the state.go(...).
For those using the ControllerAs syntax, just declare the controller label in the $routeprovider as follows:
$routeprovider
.when('/link', {
templateUrl: 'templateUrl',
controller: 'UploadsController as ctrl'
})
or
$routeprovider
.when('/link', {
templateUrl: 'templateUrl',
controller: 'UploadsController'
controllerAs: 'ctrl'
})
After declaring the $routeprovider, do not supply the controller as in the view. Instead use the label in the view.
In my case it was because of the url pattern I used
my url was like /ui/project/:parameter1/:parameter2.
I didn't need paramerter2 in all cases of state change. In cases where I didn't need the second parameter my url would be like /ui/project/:parameter1/. And so whenever I had a state change I will have my controller refreshed twice.
The solution was to set parameter2 as empty string and do the state change.
I've had this double initialisation happen for a different reason. For some route-transitions in my application I wanted to force scrolling to near the top of the page (e.g. in paginated search results... clicking next should take you to the top of page 2).
I did this by adding a listener to the $rootScope $on $viewContentLoaded which (based on certain conditions) executed
$location.hash('top');
Inadvertently this was causing my routes to be reevaluated and the controllers to be reinitialised
My issue was updating the search parameters like so $location.search('param', key);
you can read more about it here
controller getting called twice due to append params in url
In my case renaming the controller to a different name solved the problem.
There was a conflict of controller names with "angular-ui-tree" module: I renamed my controller from "CatalogerTreeController" to "TreeController" and then this controller starts to be initiated twice on the page where "ui-tree" directive used because this directive uses controller named "TreeController".
I had the same problem and after trying all the answers I finally found that i had a directive in my view that was bound to the same controller.
APP.directive('MyDirective', function() {
return {
restrict: 'AE',
scope: {},
templateUrl: '../views/quiz.html',
controller: 'ShowClassController'
}
});
After removing the directive the controller stopped being called twice. Now my question is, how can use this directive bound to the controller scope without this problem?
I just solved mine, which was actually quite disappointing. Its a ionic hybrid app, I've used ui-router v0.2.13. In my case there is a epub reader (using epub.js) which was continuously reporting "no document found" once I navigate to my books library and select any other book. When I reloaded the browser book was being rendered perfectly but when I selected another book got the same problem again.
My solve was very simple. I just removed reload:true from $state.go("app.reader", { fileName: fn, reload: true }); in my LibraryController
I have the same issue in angular-route#1.6.7, and it because the extra slash in the end of regex route:
.when('/goods/publish/:classId/', option)
to
.when('/goods/publish/:classId', option)
and it works correctly.
Just adding my case here as well:
I was using angular-ui-router with $state.go('new_state', {foo: "foo#bar"})
Once I added encodeURIComponent to the parameter, the problem was gone: $state.go('new_state', {foo: encodeURIComponent("foo#bar")}).
What happened?
The character "#" in the parameter value is not allowed in URLs. As a consequence, angular-ui-router created my controller twice: during first creation it passed the original "foo#bar", during second creation it would pass the encoded version "foo%40bar". Once I explicitly encoded the parameter as shown above, the problem went away.
My issue was really difficult to track down. In the end, the problem was occurring when the web page had missing images. The src was missing a Url. This was happening on an MVC 5 Web Controller. To fix the issue, I included transparent images when no real image is available.
<img alt="" class="logo" src="">
I figured out mine is getting called twice is because i was calling the method twice from my html.
`<form class="form-horizontal" name="x" ng-submit="findX() novalidate >
<input type="text"....>
<input type="text"....>
<input type="text"....>
<button type="submit" class="btn btn-sm btn-primary" ng-click="findX()"
</form>`
The highlighted section was causing findX() to be called twice. Hope it helps someone.