So, I have a ng-repeated list of items as such.
<li><a ng-click="{{person.id}}">Name of Person</a></li>
I would like to create a service wherein, on click, I can collect that person.id and pass it to another controller in a different route.
This would normally be very simple by just using the url and route params, however, in this case it is important that the person.id not be exposed within the browser url.
-- More Context
Whether service or not, I am needing to extract a {{person.Id}} that is data available via an ng-repeat on a list page of persons.
On click, I move from a persons controller to a new route with a "person" controller. I need that "person" controller to be able to pull the {{Person.ID}} that was clicked on the previous route in order to look up that person in a DB.
Any help would be really great!
Services aren't meant to interact directly with DOM elements. DOM should interact with directives/controllers. Controller should interact with models.
This example below demonstrates sending data from controller 1 to myFactory and then controller 2 gets it the value from myFactory.
angular
.module('app', [])
.factory('myFactory', myFactory)
.controller('myCtrl1', myCtrl1)
.controller('myCtrl2', myCtrl2);
function myFactory() {
var fromSender = null;
return {
setSender: function(sender) {
fromSender = sender;
},
getSender: function() {
return fromSender;
}
};
}
function myCtrl1(myFactory) {
var vm = this;
vm.setSender = myFactory.setSender;
}
function myCtrl2(myFactory) {
var vm = this;
vm.getSender = myFactory.getSender;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.js"></script>
<div ng-app="app">
<div ng-controller="myCtrl1 as ctrl1">
Controller 1: <br>
<button ng-click="ctrl1.setSender('from controller 1')">Send to myFactory</button>
</div>
<hr>
<div ng-controller="myCtrl2 as ctrl2">
Controller 2: <br>
value from ctrl1 via myFactory: {{ctrl2.getSender()}}
</div>
</div>
All services in Angular are singletons. So if you inject personService or something like that, in multiple controllers, then those controllers will be using the exact same object. So if you set a value on that service, then the other controllers will be able to see it.
With more code and context, I'll be able to give a more specific example.
Related
I have a utility controller build to manage documents attachments for reusing across my application.
<div ng-controller="someController">
<div ng-controller="documentController as temp1"></div>
<div ng-controller="documentController as temp2"></div>
</div>
Under the parent controller i.e. someController I have a broadcast method..
var module = angular.module("MyModule");
module.controller("someController",
function ($scope) {
$scope.$broadcast("callSomeFunctionInDocumentsController");
});
module.controller("documentController",
function($scope) {
$scope.$on("callSomeFunctionInDocumentsController", function() {
//do something here
});
});
Now the problem I am facing is that since the documentController is added twice to the view, the $on method is executed twice as well. Whereas based on some condition I would want to call the $on method either in temp1 or temp2 instance and not both.
I am not sure if what I wish to achieve is possible but any help will be much appreciated.
Thanks.
The $broadcast works simply: everyone who registered is notified through $on.
In your example, both controllers are registered.
So why do you use the same controller twice? Maybe worth to switch to component?
What about this one:
<div ng-controller="someController">
<div ng-controller="documentController as temp1"></div>
<div ng-if="oneCtrlGotNotification" ng-controller="documentController as temp2"></div>
</div>
where oneCtrlGotNotification is some flag (maybe under $rootScope).
So you will display second controller only when 1st already notified.
But it is a workaround.
One approach is to give a unique id to each element with a controller:
<div ng-controller="someController">
<div id="temp1" ng-controller="documentController as temp1"></div>
<div id="temp2" ng-controller="documentController as temp2"></div>
</div>
Then use the $attrs local to differentiate:
app.controller("documentController", function($scope, $attrs) {
$scope.$on("callSomeFunctionInDocumentsController", function() {
if ($attrs.id == "temp1") {
//do something specific to "temp1" controller
});
});
})
For more information, see
AngularJS Comprehensive Directive API Reference - controller
AngularJS $attrs Type API Reference
I have controller is named "UserController" in top of page:
<div ng-controller="UserController"><input type="text" ng-model="search"></div>
Also the same controller in bottom page from directive ng-view:
<div class="bottom" ng-controller="UserController">{{search}}</div>
Why I dont get value {{search}} in bottom part, when I fill field input in top?
Can I use one controller two times in a page?
Yes, you can use two controllers in AngularJs, Here is a demo.
What happens when I use ng-controller?
When you add ng-controller to a DOM element, angular create an instance of controller function and attaches it with that DOM, and thats why there is no two way data-binding between those divs.
How can I use data binding to share data between controllers?
You can use $rootScope variable or you can use services.
you can create service and inject in controller as dependency, so you can access its property with two way binding feature.
As said by JB Nizet, you need to have everything in the same "div".
<div ng-controller="UserController">
<input type="text" ng-model="search">
<div id="search-query">{{search}}</div>
</div>
Having the search-query at the bottom of the page is a matter of CSS, not Angular.
Controllers are not singletons. You have one controller for the top div, a second controller for the second div. One scope for the top div, one scope for the bottom div.
Both controllers have the same name, but you are ultimatally calling you controller function twice.
Some options you might want to consider to solve your problem:
Option 1) Use parent scope.
ng-model="$parent.search"
{{$parent.search}}
Option 2) Use root scope.
ng-model="$root.search"
{{$root.search}}
Option 3) Store the value in a service.
Services are singletons. If you type myService.search = $scope.search, then that value can read from the other controller.
You wont be able to watch a service variable, so perhaps you want to use the observer pattern here.
app.service("search", function() {
var listerners = [];
this.register = function(listener) {
listerners.push(listener);
};
this.update = function(searchValue) {
for(var i in listerners) {
listerners[i](searchValue);
}
};
});
app.controller("UserController", function($timeout, search){
search.register(function(searchValue) {
$timeout(function(){
$scope.search = searchValue;
});
});
$scope.$watch('search', function (newVal, oldVal, scope) {
search.update(newVal);
});
});
Option 4) Broadcast the new value.
$scope.$watch('search', function (newVal, oldVal, scope) {
$rootScope.$broadcast('search', newVal);
});
$scope.$on('search', function(event, data) {
$scope.search = data;
});
You can have multiple instances of the same controller in your page. They share the same functionality. But every instance of that controller is getting his own $scope. So in your first controller $scope.search can be 'mySearch', but the second controller won't get this, because it's another $scope.
You can do two things:
You can put the controller on a containing element, let's say the body, so both your input and your div are within the same $scope.
OR, if you want them to be seperate, you can use a service to share the search.
Your HTML:
<div ng-app="myApp">
<div ng-controller="UserController">
<input type="text" ng-model="search.mySearch"/>
</div>
<div ng-controller="UserController">
{{search.mySearch}}
</div>
</div>
Your Javascript:
var myApp = angular.module('myApp', []);
myApp.factory('Data', function(){
return { mySearch: '' };
});
myApp.controller('UserController', function( $scope, Data ){
$scope.search = Data;
});
See Fiddle
I am trying to understand Angularjs behaviors.
I am building a web-app, and I want the CurrentUser's info be shared among all the app components. To do that, I have created a CurrentUserController bound to $rootScope. This controller is used by a user directive utilized in the body html element, so that it is globally accessible and it's created just one time.
app.controller('CurrentUserController', function ($rootScope)
{
// initialization
$rootScope.userCtrl = self; //<- MAKE IT GLOBAL
this.islogged=false;
this.name="";
var self = this;
// functions
this.isLogged = function()
{ return self.islogged; };
this.setLoggedIn = function(credentials)
{ self.islogged = true; };
this.setLoggedOut = function()
{ self.islogged = false; };
}
);
app.directive('currentUser', function() {
return {
controller:'CurrentUserController'
};
})
and then my html page
<html>
...
<body current-user>
...
</body>
</html>
However I read that Services should be used to share data between controllers, since they are singleton.
So my question is:
is my approach wrong, or it is equivalent as I utilized services?
Moreover, right now I can utilize the directive ng-switch calling $rootScope.userCtrl functions, like this:
<div id="nav-right-side" class="navbar-right" ng-switch on="userCtrl.isLogged()">
<div ng-switch-when="false">
<login-button></login-button>
</div>
<div ng-switch-when="true">
<loggedin-button></loggedin-button>
</div>
</div>
If I utilize services, would I still be able to do that?
Thank you
The $rootScope is indeed shared across all the app and it is also best to store models into services.
Why bother with services ?
Because of the $digest cycle. Each time a watched value is modified, the digest is triggered. In angular, by default the digest is a loop that goes down all your scope from the $rootScope down to its leafs. On each element, it has to get if the value has been modified or not to update the view accordingly. This is pretty expensive, and it is the cause of why angular can be slow on big applications. Keeping the scope as light as possible is how you can build complex apps in angular. That's why storing things is always better in services, you do not pollute the scope with data you could put somewhere else.
That being said, auth is peculiar because you want to access the same data from the view and services. You can store it in the $rootScope as Asta puts it but I do not think that is consistant with best practices. This is opinionated
What can be done is creating a service that will hold you model and share it through a controller to have access to it from both the view and the other services/models.
Session.js
function Session(){
var
self = this,
_islogged=false,
_name = '';
// functions
this.isLogged = function() {
return self.islogged;
};
this.setLoggedIn = function() {
self.islogged = true;
};
this.setLoggedOut = function() {
self.islogged = false; };
}
// GetUsername, setUsername ... Whatever you need
}
angular
.module('app')
.service('Session', Session);
rootController.js
function rootController(Session){
// share the Session Service with the $scope
// this.session is like $scope.session when using the controllerAS syntax.
this.session = Session;
}
angular
.module('app')
.controller('rootController', rootController);
I would suggest you take a look at these articles:
Techniques for Authentification in AngularJs Applications
Comprehensive 10 000 words tutorial in angular
Diving into controllerAs syntax
Your best to use a Service to share data as you mention. In your approach you've used a Controller in a way that its not really intended.
You can call your controller from your HTML by using ng-controller so something like the following should work. This would be useful for a Login view for example or a logout directive.
<div ng-controller="userCtrl">
<div id="nav-right-side" class="navbar-right" ng-switch on="isLogged()">
<div ng-switch-when="false">
<login-button></login-button>
</div>
<div ng-switch-when="true">
<loggedin-button></loggedin-button>
</div>
</div>
</div>
In order to have your session available globally for use elsewhere you can use a service which you can initialise from your app. The session data can be added to $rootScope which you can then reference from any view or controller.
Service
angular.module('app').service('session', function($rootScope) {
$rootScope.sessionData.loggedIn = true
// extra logic etc..
});
Main App
angular.run(session)
angular.module('app').run(function(session) {});
Then to reference the variable from your view
<div id="nav-right-side" class="navbar-right" ng-switch on="sessionData.isLoggedIn">
Note: its good practice to use an object with scope variables to help avoid issues with inheritance.
I have a constant sidebar in my index.html file that lists projects using ng-include. When a project is created, or updated etc.. I would like the sidebar to automatically update along with it. I'm not sure which part of my code to provide, as hopefully it's a fundamental question that's easy to answer, though the solution eludes me.
Edit: feel I'm almost there, but src doesn't seem to pick up the controller property:
<div class="col col-md-4" data-ng-controller="ProjectsController" data-ng-include src="'{{sidebarUrl}}'"></div>
In my projects controller:
// Update existing Project
$scope.update = function() {
var project = $scope.project ;
project.$update(function() {
$location.path('projects/' + project._id);
$scope.$broadcast('projectUpdated');
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
$scope.sidebarUrl = 'modules/projects/views/list-projects.client.view.html';
$scope.$on("projectUpdated",function(event,args) {
$scope.sidebarUrl=null;
$scope.sidebarUrl= 'modules/projects/views/list-projects.client.view.html';
});
This is where services are your friend. You should start by encapsulating your CRUD operations into a service.
function MyCrudService($http, ...){ ... }
angular.module('my-app')
.service('myCrudService', MyCrudService);
Now, there are several ways you could implement the updating.
Use $rootScope and broadcast a message saying something has changed, and listen for that event in your sidebar controller (assuming you have one).
//Inside your service
function updateProject(proj){
//Update project
$rootScope.$broadcast('project-updated', proj);
}
//Inside your controller
function MySidebarController($scope){
$scope.$on('project-updated', function(){ ... });
}
Encapsulate the eventing logic inside your service to avoid using $rootScope. Just maintain your own list of callbacks and execute them.
//Inside your controller
function MySidebarController(myCrudService){
myCrudService.onProjectChanged(function(){ ... });
}
Expose the shared data on your service that can be databound to.
//Inside your controller
function MySidebarController($scope, myCrudService){
$scope.projects = myCrudService.projects;
}
Personally, I try to avoid $scope in my controllers, but using it for eventing is OK. Still, I might write some kind of directive that would allow me to execute an expression whenever an event fired in order to avoid it.
<my-event-binding event='project-updated' expression='sideBar.updateProjects()' />
Okay, so I had the same requirement(dynamically changing menu items in an included side panel) what I did was to use a controller in the ng-include template. The template would then fetch the relevant menu items from a service and update the controller. The view had an ng-repeat directive to show all the menu items (projects in your case).
<div ng-controller="ProjectsCtrl">
<ul>
<li ng-repeat="project in projects">
<a ng-href="project.url">
{{project.name}}
</a>
</li>
</ul>
</div>
The controller function could look something like:
function($scope, projectsSvc){
$scope.projects = [];
loadProjects();
$scope.$on("updatedProjects", loadProjects);
function loadProjects(){
projectsSvc.getProjects.success(function(projects){
$scope.projects = projects;
});
}
}
Projects are fetched from a service. When you update a project, broadcast an event that triggers a load of the projects again.
So after the new projects have been committed into the service backend, the sidebar will update accordingly.
I have a page that uses two controllers. One is associated with the header (which is reused on many different pages) and the other is associated with the body of the page. The mark-up looks something like:
<div id = 'header' ng-controller = 'headerController' >
<div id = 'login-button' ng-click = 'login()'>
Login
</div>
</div>
<div id = 'body' ng-controller = 'myPageController'>
....
</div>
The controllers are defined in two separate JS files respectively:
headerController.js
myPageController.js
I would like to bind a listener to the login() function so that whenever the button associated with it is clicked, another function is called in the myPageController.js file.
How would I go about doing this in Angular?
Probably the best way is to $broadcast() an event from the $rootScope that your page controller handles using $scope.$on().
Example:
headerController.js:
function headerController($scope, $rootScope) {
$scope.login = function () {
$rootScope.$broadcast('login');
};
}
myPageController.js
function myPageController($scope) {
$scope.$on('login', function () {
// handle login event here
});
}
$rootScope.$broadcast() is fairly slow. If you just want two unrelated controllers to communicate, this is best done through a service. For example, see my answer at:
Communicating between a Multiple Controllers and a directive
Hope that helps!