For quick reading, the problem is simplified under "The problem", and for further information keep going down for background and notes before answering. Thank you!
The problem
I want to delete the instance of a controller when refreshing the page or when moving to a different view and then returning to the same view through the navigator (nav.html). In fact, every time view X.html is visited, I want the program to check if X-controller.js exists, and if it does delete it before making a new instance.
How far am I going here, is it a 2 line solution I failed to find online or am I looking at hours of coding to make this work?
Background
My project uses the $routeProvider service, not the the ng-controller directive. Once the app launches there are constantly two views, one on the top where you can navigate back and forth through the controllers "Home - Contact - Support" (logically, nav.html), and one on the bottom which is the "Home" or "Contact" and so on.
I haven't had any problems with this arrangement until the code begun making massive calculations. The same instance of the controller is updated with more data than it should, calculates for previous data that was discarded, and so on. I've read online about deleting the controller but as far as I know it's not that easy.
Notes before answering the question:
If the second option of 'hours of coding' is the solution I'm not expecting anyone to do this for me, but references to articles or code for that would be appreciated because I haven't found anything useful on my own.
If there is an easier solution that applies only when ng-controller is used and not $routeProvider then it's not an option for me. There are over 20 views and many sections of code which triggers redirection to a different view with a different controller using event listeners. I'm not currently planning on changing $routeProvider to ng-controller.
If the solution doesn't actually delete the previous instance, rather clears the $scope and javascript variables then that could work for me as well.
I haven't included code because this question is not about a bug or error, if for some reason code snippets of the $routeProvider configuration or one view and controller is needed let me know and I'll include that code with the classified sections replaced with similar dummy code.
Clarification Edit
I'll illustrate with an example. Assume X.html is a view controlled by XCtrl.js. $scope.test is initiated in the beginning $scope.test = 2 of that controller, and once a button in the view is clicked $scope.test becomes 3. Also, the X view displays $scope.test all the time. So I moved to that view, clicked the button, and saw that 3 is displayed on the screen. Then I moved to "Home" through the navigator, then back to "X", and 3 is still displayed. But what I want is 2 to be displayed, and not 3. I want everything to be renewed in that controller.
Solution
Eventually I used a different technique to solve this. All the data saved in the local storage was affecting the $scope variables (there were too many variables to track that I didn't notice this). To solve the issue I cleared the local storage key localStorageService.set('keyUsed', []); once the view controlled by controller X is visited. Assume an init function, so the line of code clearing the local storage was placed in the top of that function.
I'm still marking the correct solution from the answers below for the problem I initially thought I had.
Always have a '.' in your ng-models!
-- Miško Hevery (father of AngularJS)
Most likely you have an issue with $scope.test and not with controller itself. If your template x.html refers the value of test as {{test}} (without any prefix) then most probably you are referring the test of the wrong scope. It usually happens due to prototypical chain of scopes that extend one another and fallback to prototype property value. In this case, choose something unique for XCtrl controller and put all your state inside this controller to that namespace. For example, use x as namespace
$scope.x = {};
$scope.x.test = 2;
instead of just
$scope.test = 2;
Then in x.html refer the value as
{{x.test}}
This is one of the Angular best practices.
Another best practice, that may solve your issue, is actually not using $scope at all, but use controller instance itself for storing state in junction with controllerAs syntax:
index.html
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.min.js#1.5.8" data-semver="1.5.8" src="https://code.angularjs.org/1.5.8/angular.min.js"></script>
<script data-require="angular-route.min.js#1.5.8" data-semver="1.5.8" src="https://code.angularjs.org/1.5.8/angular-route.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-view>
</body>
</html>
script.js
angular.module('app', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/x', {
controller: 'xCtrl',
controllerAs: 'x',
templateUrl: 'x.html'
})
.when('/y', {
controller: 'yCtrl',
controllerAs: 'y',
templateUrl: 'y.html'
})
.otherwise({
redirectTo: '/x'
})
}])
.controller('xCtrl', [function() {
var x = this;
x.test = 2;
x.doSomething = function() {
x.test ++;
};
}])
.controller('yCtrl', [function() {
var y = this;
y.hello = 'Hello';
}])
x.html
<h1>X.html</h1>
<p>test = {{x.test}}</p>
<button ng-click="x.doSomething()">+1</button>
Link to Y
y.html
<h1>Y.html</h1>
<p>{{y.hello}}</p>
Link to X
Live Demo:
https://plnkr.co/edit/EpUn94uWMliTaG5mvPxv?p=preview
Links:
To understand the issue with not having a '.' in your model, see this video by Miško Hevery.
To better understand controllerAs approach, you can read this post by Todd Motto.
Related
I'd like some advice on how to share some data between two or more controllers in AngularJS.
For now I'm just dealing with two controllers, but in the future I will have more controllers that will also want to use this same data. Right now I have a navigation-controller which is controlling the side navigation and the header. And for ease of understanding, let's say the second controller is called content-controller which is responsible for dealing with all the content.
I want to dynamically load the content based on whatever the user searches for and the search bar is in the side navigation, so this searchTerm needs to be accessible by both controllers. In the future, I would also implement some other features which would probably need to access this searchTerm as well.
In terms of the HTML structure, the content-controller is inside the navigation-controller.
My first thought was to make searchTerm globally available by sticking it in $rootScope, but I'm unsure if this is an efficient/secure way to do it.
My second thought was to take the searching aspects and put them into a service. Inside this service I would put functions which would speak to the API in order to get the necessary data. This would mean on the search bar, I can make the submit search button access the service and run something like FooService.update(searchTerm).
What do you think the best way to deal with this scenario is?
Sharing data between controllers has always been a prominent requirement. You have a couple of options out there :
Factory
Services
You can refer to this answer, for more details upon the differences.
Using services is definitely the better option, since you won't be polluting the root scope with extra variables [That are destined to grow in numbers as your have already mentioned].
A possible way to store your data in services, and access them in controllers and HTML effortlessly can be described as :
Create a service, that will hold all the model variables.
angular.service("dataService", function() {
this.value1 = "";
this.value2 = "";
});
reference that service in your controllers, saving their reference in the scope.
angular.controller("myCntrl1", function($scope, dataService) {
$scope.dataService = dataService;
});
angular.controller("myCntrl2", function($scope, dataService) {
$scope.dataService = dataService;
});
Now in your html, you refer all your modal variables using the service reference :
// Controller 1 view
<div ng-controller="myCntrl1">
<input type="text" ng-model="dataService.value1" />
</div>
// Controller 2 view
<div ng-controller="myCntrl2">
The value entered by user is {{dataService.value1}}
</div>
First of all i don't know whether it's gonna work for you.
You can use local storage.
By using this the same data can be accessed in any controller
Here's an example how it worked for me.
app.controller("loginCtrl", function($scope, $window){
$scope.submit = function(){
$window.localStorage.setItem = ("username", $scope.username);
};
});
app.controller("homeCtrl", function($scope, $window){
$scope.logout = function(){
$window.localStorage.getItem = ("username");
};
});
I'm using ui.grid to get a list of parts. I've created a column that contains a button which launches a modal. What I'm having trouble with is sharing the scope of the part that is contained in the row. I want to share the properties of that row with the the button that I'm creating using cellTemplate. I then want to share the $scope of the part row with the modal that it will launch.
I'm a bit stumped on how to actually do this.
So far I've tried
• Wrapping an ng-repeat around the button that I want to target. This kind of works but makes the app super slow
• Data-binding on the button via ng-class. I can't seem to target this correctly.
How can you share the $scope of an object that you're receiving via $http.get into the ui.grid with elements that you're creating with cellTemplate?
Disclaimer -- I always use controllerAs syntax, so if referencing the controller in the context of HTML is weird to you, just ignore that part and pretend like you setup the methods to be directly on the scope. I also do everything in Typescript, not Javascript, so I'm going to write the pertinent parts of the code in here. They should be easy to plug into your application.
The answer is a combination of the two answers you already have from Sunil and S.Baggy.
What you want to do is use the getExternalScopes() function and attach something to the scope of the HTML where your grid resides. The thing you handed the grid will take in the row and call your modal popup. See below for a little clarification.
Your HTML -
<div ng-controller="MyController as myController">
<div ui-grid="myController.GridObject" external-scopes="myController"></div>
</div>
By using controllerAs syntax and making the controller the reference in the external scopes, we can now gain access to everything in our controller. So we can call methods in it.. In order to do that, however, we have to use a cellTemplate, which it sounds like you already know how to do, and in that cellTemplate we have to have the following:
ng-click="getExternalScopes().methodToLaunchModal()"
Now the last part of hooking all this up is to write the methodToLaunchModal() method into the controller. For that we're borrowing the code from S.Baggy's answer. Here is a very abbreviated controller with the GridObject (the same one I referenced from the controller above):
app.controller('MainCtrl', function($scope, $modal) {
GridObject = {
... setup of all the other things
columnDefs: [{ etc, etc, }, { etc, cellTemplate: '<div ng-click="getExternalScopes().methodToLaunchModal(row.entity)">whatever</div>' }]
};
methodToLaunchModal: function(row) {
var modalInstance = $modal.open({
templateUrl: 'someTemplate',
controller: 'ModalController',
resolve: {
rowObject: function () { return row; }
}
});
};
});
At this point your modal scope will have an object named rowObject on it that will have all the properties from your row. So you should be able to call rowObject.SomeProperty to get its value.
Apologies if any of the syntax is slightly off.
I use the bootstrap $modal directive with code like this...
clickFunction: function (event, row) {
event.stopPropagation(); // prevents the current row from appearing as selected
var modalInstance = $modal.open({
templateUrl: 'views/modalcontent.tpl.html',
controller: 'ModalMessageController',
size: 'lg',
resolve: {
message: function () { return row.entity.serial_number; }
}
}
);
Then I just refer to {{message}} in the template. Of course you could pass in any other piece of data too.
You can access row and its properties on row selection or ng-click of that row using externalscopes
ng-click="getExternalScopes().onRowClick(row)"
onRowClick: function (row) {
row.entity.Property1; /// and so on for all row properties
}
I have a user.list.ctrl and a user.detail.cntr. All the controllers are build as a module and are injected in a "user-module" which I inject in the app.js. (see the complete code in the plunker below)
my controller module
angular.module('user-module', ['user-module.controllers']);
my user-module
angular.module('demo.app', ['user-module']);
In both controllers i inject user-Fctr with data from a REST factory. (works well)
user.list.cntrl has a $scope.refresh()
user.detail.cntrl has a $scope.update()
user.list.cntrl
When I enter a new record, i call the $scope.refresh() so I can refresh the list. (this is working fine)
user.detail.cntrl
When i click a user from the list, the user detail loads in a different view (works ok)
when I update the user.detail, I want to call $scope.refresh() to update the user.list , but it is not working. I cannot call $scope.refresh()
I thought that since I inject the same factory into both controllers I can use each others $scopes.
Any ideas on how I can use $scope.refresh() (or update the list when I update the user.detail.js)
I make a plunker with all the js files (the plunker is not functional, it is only to show the code that I have)
http://plnkr.co/edit/HtnZiMag0VYCo27F5xqb?p=preview
thanx for taking a look at this
This is a very conceptual problem.
You have created a controller for each "piece" of view because they are meant for different activities. This is the purpose of controllers. So that is right.
However, you are trying to access the refresh function, written in one controller, in another one. Taken literally, this is wrong, since then, refresh is out of place either inside the user list controller or the detail controller.
A function that is meant to control (literally) what is happening on a specific piece of view is a controller. - There you are right having a controller for the list and one for the details.
A function that is meant to be shared between controllers must be a service. This is exactly what you want for your refresh function to be.
Whenever you inject the same factory into n controllers, you can't use the scope of every controller. This isn't the purpose of a controller.
However, whenever you inject the same factory into n controllers, you can use its exposed methods.
The problem you have, can be solved as follows:
app.factory( 'sharedFunctions', [ 'factoryId', function sharedFunctions( factoryId ) {
var refresh = function () {
factoryId.getAll(/*your params to query*/)
.success( function ( response ) {
//This will return the list of all your records
return response;
});
};
return sharedFunctions;
}]);
With this factory service registered, then you can inject it to your controllers and whenever you need to refresh, just call the exposed method of the service and plot the new information into the view.
Hope it works for you!
i ended up doing this:
I added in the list.contrl this:
factoryId.listScope = $scope;
since I already have the factoryId (my data service) injected in the detail controller, I can call this:
factoryId.listScope.refresh();
it works but I don't know if this is the best way. any comments?
My problem is I have one ng-app. Does that mean I have to do dependency injection for plugins I may not be using on that given view? Example I bring in ngTagsInput does that mean I have to do it even when the view doesn't call for it? That would mean I have to include that js for every view even if it doesn't use ngTagsInput.
I have a very large MVC .NET application and I am trying to figure out what is he best way to handle bringing in external plugins.
I have some code like so in our Main _Layout template:
<html ng-app="ourApp">
<head>
<!-- all of our includes are here -->
</head>
<body>
<directive></directive>
<anotherdirective></anotherdirective>
#RenderBody()
</body>
</html>
RenderBody is where MVC slides in our views from our mvc routing.That view may look like so:
<script src="~/Scripts/HomeAngular.js"></script>
<div ng-controller="HomeCtrl">
<directive3></directive3>
</div>
App JS:
var app = angular.module('ourApp', ['ngTagsInput']);
IS there a way I can get around having to inject ngTagsInput on every view page even if i don't need it?
There are several different ways to handle Dependency Injection (DI) in angular. In this first example, you simply define ngTagsInput before declaring the controller. If ngTagsInput is a service, for example, you'll need to return an object with methods that allow you to access the data inside of that service.
For example:
app.service('ngTagsInput', function($scope) {
var data = {};
return {
getData = function() {
return data;
},
setData = function(val) {
data = val;
}
};
});
app.controller('HomeCtrl', function($scope, ngTagsInput) {
// ...
$scope.tags = ngTagsInput; // whatever you want to do with it
});
However, there's a problem...
In the example above, if you run that code through a minifier, you'll get $scope turned into some variable (a, b, x, etc...) and angular wont know what to do with that.
In this method, we use an array. The last item in your array is your lambda function for the controller, which takes 1 argument for each previous item in the array:
app.controller('HomeCtrl', ['$scope', 'ngTagsInput', function(scp, ngTagIn) {
// ...
}]);
This code can be run through a minifier just fine, since the dependencies are strings in the array and can be renamed to anything you want inside the function's parameters.
DI also works in directives, factories, filters, and services with the same syntax; not just controllers and modules.
app.directive('HomeCtrl', ['$scope', 'ngTagsInput', function(scp, ngTagIn) {
// ...
}]);
Couldn't you break down your application further into smaller modules instead of just one. Then you can inject the smaller modules into the app module and only inject the ngTagsInput dependency on the modules that actually need it. That's how I typically break up my application by function or area.
New in angularJS, I would like to know what are the pros and cons between the codes below?
Which is recommended to use?
$routeProvider.when('foo', {
templateUrl: 'foo.html',
controller: fooCtrl
function fooCtrl() {
//something here
}
});
or
$routeProvider.when('foo', {
templateUrl: 'foo.html'
});
app.controller("fooCtrl", function() {
//something here
});
//in html
<div ng-controller="fooCtrl"></div>
I prefer the second approach, and use it when developing our application.
It is the elegant way of coding , seperating your routes-configuration, module-wiring etc from the Controllers. we can write the routes-config in a main file say app.coffee [I use coffeescript] defining like
routesConfig = ($route) ->
$route.when('/employees',
{templateUrl: 'employee.employeeView.html'})
Define the routesconfig and wiring modules [eg: employee.employeeController] here.
modules = ['employee.employeeController', 'user.userController']
you can create, start your angular application from here,
m = angular.module('app', modules)
m.config['$route', routesConfig]
Now you can specify the Controllers seperately, say in employeeController.coffee
name = 'employee.employeeController'
mod = angular.module(name, [])
mod.controller(name, [
'$scope'
'$log'
($scope, $log) ->
$scope.name = 'java'
In your View, say employeeView.html
<div ng-controller="employee.employeeController">
<div class ="info">
Name is {{name}}
</div>
Basically we seperated Controllers, View , application configuration from each other.
To add something specific to your question,
If you are using the first approach, then probably you are using your controller as a Route Controller, and in
second approach , it is a View Controller.
In both cases, controller will be instantiated for the mentioned route.
For instance, I have a main page index.html and i am adding many views (ng-view) in the basic html template.
If you have two different sections of view in this template say 'section1' and 'section2' and each of them are included
with ng-view, then you probably need two different controllers, and is good to define them using the second approach.
Use this type of controller to initialize the scope with data,functions,watches etc and refer the controller in your view using ng-controller.
If you have a section, say 'section1' [representing the main html page] that is included via ng-view which encapsulates both section1 and section2,
then that view needs route controller.
Do not use both the approaches for a single view/route as it would result in creating two instances of same controller
for the same route.
I like to add two links here which eloborates this (your query) and address this query (problem in defining controllers in two places)
https://groups.google.com/forum/?fromgroups=#!topic/angular/52zE0ibOAgk
AngularJS can a $on method be called more than once for a single $broadcast?