Say I have several forms each with their own controller that look like this
<div ng-controller="MyController1 as ctrl">
<label>Some label </label>
</div>
<div ng-controller="MyController2 as ctrl">
<label>Some label </label>
</div>
And I have a global controller, that gets the information about the form names. Now I want to find the controllers for each form. For instance, if in my global controller function, I get the name of the first form, how can I find out that its controller is MyController1? Is that even possible?
Calling a controller from another controller is possible. But, I believe the problem you're trying to solve is somewhere else: the architecture of your app.
Ideally, your app should 'react' to changes on the state. The state should be kept in a single place (also called 'single source of truth'), ie. a service. Then you share that service state with as many controllers as you need.
You can either update the service state directly from the controller, or by calling a method on the service itself.
Look at the example below. I hope that sheds some light.
Cheers!
angular.module('app', [])
.service('MyService', function(){
var self = this;
self.state = {
name: 'John'
};
self.changeNameFromService = function() {
self.state.name = 'Peter';
}
})
.controller('Ctrl1', function($scope, MyService){
$scope.state = MyService.state;
$scope.changeName = function(){
// update the state of the scope, which is shared with other controllers by storing the state in a service
$scope.state.name = 'Mary';
}
})
.controller('Ctrl2', function($scope, MyService){
$scope.state = MyService.state;
// call a method defined in service
$scope.changeName = MyService.changeNameFromService;
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="Ctrl1">
Ctrl1: {{state.name}}
<button ng-click="changeName()">Change name!</button>
</div>
<div ng-controller="Ctrl2">
Ctrl2: {{state.name}}
<button ng-click="changeName()">Change name from service!</button>
</div>
</div>
Related
I'm working on a small app in AngularJS. My project contain a Body.html file that contain 3 views: SideMenu, Header and Content, each with each own Controller and a MainController as there parent - the controller of the Body.html.
Can the header's controller change a property in the side-menu - the open/close status of the side-menu.
And Can the side-menu controller change a property in the header - the header's text.
I can use the main controller, since both of the header's controller and the side-menu controller can reference the main controller. But the data won't be consist. Updating the data from the 1st controller wan't effect the data in the 2nd controller (without the use of $watch).
Can both the side-menu's controller and the header's controller (sibling controllers) communicate with each other? without the help of there parent?
Body.html
<div>
<!-- Header placeholder -->
<div ui-view="header"></div>
<!-- SideMenu placeholder -->
<div ui-view="sideMenu"></div>
<!-- Content placeholder -->
<div ui-view></div>
</div>
Header.html
<div>
{{ headerCtrl.text }}
</div>
<div ng-click="headerCtrl.openSideMenu()">
--Open--
</div>
HeaderController.js
// sideMenuCtrl = ???
headerCtrl.text = "Title";
headerCtrl.openSideMenu = function()
{
sideMenuCtrl.isSideMenuOpen = true;
};
SideMenu.html
<div ng-class={ 'side-menu-open': sideMenuCtrl.isSideMenuOpen }>
<div ng-repeat="menuItem in sideMenuCtrl.sideMenuItems"
ng-click="sideMenuCtrl.selectMenuItem(menuItem)">
{{ menuItem.text }}
</div>
</div>
SideMenuController.js
// headerCtrl = ???
sideMenuCtrl.selectMenuItem = function(menuItem)
{
headerCtrl.text = menuItem.text;
}
As stated in my comment, you can use an AngularJS service to share some data between your controllers.
app.service('AppContextService', function(){
this.context = {
isSideMenuOpen: false
};
});
app.controller('SideMenuCtrl', ['$scope', 'AppContextService', function($scope, AppContextService) {
// exposing the application context object to the controller.
$scope.appContext = AppContextService.context;
}]);
app.controller('HeaderCtrl', ['$scope', 'AppContextService', function($scope, AppContextService) {
$scope.appContext = AppContextService.context;
$scope.openSideMenu = function()
{
$scope.appContext.isSideMenuOpen = true;
};
}]);
Then adapt the HTML to use your shared appContext object.
<div ng-class={ 'side-menu-open': appContext.isSideMenuOpen }>
[...]
</div>
Here is a working fiddle that illustrates the issue: fiddle
This answer covers the use of a service to fit your needs but I am sure that there are other (and perhaps better) ways to tackle the problem which might involve other Angular feature, or even some overall application refactoring.
To dig a little deeper, this SO topic might be a good start: difference between service, factory and providers
I`m new to angularJS. I have a table, I make a request and output some default data in my tablerows from Controller2. Now I want to make a different request and output other data in the same table,but this time from Controller1. How can I do this ?
Html
<tr ng-repeat="stats in statsFromTable" ng-show="statsFromTable.length > 0">
<td><span ng-bind="stats.PERS"></span></td>
JavaScript
Controller2 -- > $scope.statsFromTable = data.REQUEST_ATTRIBUTES.historyList; // the first default request
Controller1 -- > ???
Don't use $rootScope to share info between controllers. For that purpouse you can use a service.
So your code could look like:
angular.
module('yourAngularModule', []).
controller('MyController', ['$scope','myService', function ($scope, myService) {
$scope.number1 = myService.number;
}]).
controller('MyController2', ['$scope','myService', function ($scope, myService) {
$scope.number2 = myService.number;
}]).
service('myService', [function() {
var service = {
number:0
};
return service;
}]);
})(window.angular);
And then you could bind the data like:
<div ng-controller="MyController">
<h3>Controller 1</h3>
{{number1}} // Will display service.number --> 0
</div>
<div ng-controller="MyController2">
<h3> Controller 2</h3>
{{number2}} // Will display service.number --> 0
</div>
I did a plnkr where you can take a look at how two different controllers share info via service.
you can use $rootScope to do this. here is the example demo for you i have created
I have angular working in one of my ASP.NET MVC applications. I am using two html templates with Angular Routing. One is a list of current Favorites that comes from the database and is serialized into json from my Web API and used by angular to list those items from the database.
The second html template is a form that will be used to add new favorites. When the overall page that includes my angular code loads, it has a cookie named currentSearch which is holding the value of whatever the last search parameters executed by the user.
I would like to inject this value into my angular html template (newFavoriteView.html) for the value of a hidden input named and id'd searchString.
I have tried using jQuery, but had problems, plus I would much rather do this inside of angular and somehow pass the value along to my template or do the work inside the view(template). However, I know the latter would be bad form. Below is the code I think is important for one to see in order to understand what I am doing.
Index.cshtml (My ASP.NET VIEW)
#{
ViewBag.Title = "Render Search";
ViewBag.InitModule = "renderIndex";
}
<div class="medium-12 column">
<div data-ng-view=""></div>
</div>
#section ngScripts {
<script src="~/ng-modules/render-index.js"></script>
}
Setting the cookie in the MVC Controller
private void LastSearch()
{
string lastSearch = null;
if (Request.Url != null)
{
var currentSearch = Request.Url.LocalPath + "?" +
Request.QueryString;
if (Request.Cookies["currentSearch"] != null)
{
lastSearch = Request.Cookies["currentSearch"].Value;
ViewBag.LastSearch = lastSearch;
}
if (lastSearch != currentSearch)
{
var current = new HttpCookie("currentSearch", currentSearch){
Expires = DateTime.Now.AddDays(1) };
Response.Cookies.Set(current);
var previous = new HttpCookie("lastSearch", lastSearch) {
Expires = DateTime.Now.AddDays(1) };
Response.Cookies.Set(previous);
}
}
}
render-index.js
angular
.module("renderIndex", ["ngRoute"])
.config(config)
.controller("favoritesController", favoritesController)
.controller("newFavoriteController", newFavoriteController);
function config($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "/ng-templates/favoritesView.html",
controller: "favoritesController",
controllerAs: "vm"
})
.when("/newsearch", {
templateUrl: "/ng-templates/newFavoriteView.html",
controller: "newFavoriteController",
controllerAs: "vm"
})
.otherwise({ redirectTo: "/" });
};
function favoritesController($http) {
var vm = this;
vm.searches = [];
vm.isBusy = true;
$http.get("/api/favorites")
.success(function (result) {
vm.searches = result;
})
.error(function () {
alert('error/failed');
})
.then(function () {
vm.isBusy = false;
});
};
function newFavoriteController($http, $window) {
var vm = this;
vm.newFavorite = {};
vm.save = function () {
$http.post("/api/favorites", vm.newFavorite)
.success(function (result) {
var newFavorite = result.data;
//TODO: merge with existing topics
alert("Thanks for your post");
})
.error(function () {
alert("Your broken, go fix yourself!");
})
.then(function () {
$window.location = "#/";
});
};
};
favoritesView.html
<div class="container">
<h3>New Favorite</h3>
<form name="newFavoriteForm" ng-submit="vm.save()">
<fieldset>
<div class="row">
<div class="medium-12 column">
<input name="searchString" id="searchString" type="hidden"
ng-model="vm.newFavorite.searchString"/>
<label for="title">Name</label><br />
<input name="title" type="text"
ng-model="vm.newFavorite.name"/>
<label for="title">Description</label><br />
<textarea name="body" rows="5" cols="30"
ng-model="vm.newTopic.description"></textarea>
</div>
<div class="medium-12 column">
<input type="submit" class="tiny button radius" value="Save"/> |
Cancel
</div>
</div>
</fieldset>
</form>
</div>
My current attepts have been using jQuery at the end of the page after Angular has loaded and grab the cookie and stuff it in the hidden value. But I was not able to get that to work. I also thought about setting the value as a javascript variable (in my c# page) and then using that variable in angular some how. AM I going about this the right way?
Or should it be handled in the angular controller?...
I'm new to angular and the Angular Scope and a bit of ignorance are getting in the way. If any other info is needed I can make it available, thanks if you can help or guide me in the right direction.
You can do it by reading the cookie value using JavaScript, set it as a property of the $scope object and access it on the template.
//Inside your controllers
function favoritesController($http, $scope) {
//Get the cookie value using Js
var cookie = document.cookie; //the value is returned as a semi-colon separated key-value string, so split the string and get the important value
//Say the cookie string returned is 'currentSearch=AngularJS'
//Split the string and extract the cookie value
cookie = cookie.split("="); //I am assuming there's only one cookie set
//make the cookie available on $scope, can be accessed in templates now
$scope.searchString = cookie[1];
}
EXTRA NOTE
In AngularJS, the scope is the glue between your application's controllers and your view. The controller and the view share this scope object. The scope is like the model of your application. Since both the controller and the view share the same scope object, it can be used to communicate between the two. The scope can contain the data and the functions that will run in the view. Take note that every controller has its own scope. The $scope object must be injected into the controller if you want to access it.
For example:
//inject $http and $scope so you can use them in the controller
function favoritesController($http, $scope) {
Whatever is stored on the scope can be accessed on the view and the value of a scope property can also be set from the view. The scope object is important for Angular's two-way data binding.
Sorry if I'm misunderstanding or over-simplifying, but...assuming JavaScript can read this cookie-value, you could just have your controller read it and assign it to a $scope variable?
If JavaScript can't read the value, then you could have your ASP write the value to a JavaScript inline script tag. This feels yuckier though.
Update to show controller-as example.
Assuming your HTML looked something vaguely like this:
<div ng-controller="MyController as controller">
<!-- other HTML goes here -->
<input name="searchString" id="searchString" type="hidden" ng-model="controller.data.currentSearch"/>
Then your controller may look something like this:
app.controller('MyController', function ($scope, $cookies) {
$scope.data = {
currentSearch: $cookies.currentSearch
};
// Note that the model is nested in a 'data' object to ensure that
// any ngIf (or similar) directives in your HTML pass by reference
// instead of value (so 2-way binding works).
});
In my angular app I have a product view/controller that has the common questions for product (SKU, Name, Description etc). I also have a dropdown field named ProductType that I will use to load a dynamic view/js/controller for questions that vary based on that product type. When the user saves the product I'll have a property on the base product model named ProductTypeConfig (as well as ProductType) that contains a json representation of the product type configuration and I want to pass all that to the server controller for persistence.
Has anyone seen this done before in Angular? Comments or clues as to how to go about this? I don't want to load all of js for every product type controller etc. ahead of time as this will potentially be plugable by the client as new product types are rolled out.
EDIT:
Ok, so I created a plunk to demonstrate what I'm trying to accomplish. I have the dynamic piece working well I think. At this point I just need to figure out how to grab the dynamic data in the saveProduct() function in the ProductController. When save is clicked, I need to somehow call a method on either the TypeAController or the TypeBController depending on which one is loaded. I was thinking that I could probably create a service that all the controllers would depend on and have it do the work. Is this something that is possible?
The plunk is located here http://plnkr.co/edit/6kQYKU
This is the main controller:
(function() {
var app = angular.module('ProductApp', ['ngRoute']);
app.config(function($httpProvider, $routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'product.html',
controller: 'ProductController'
}).
otherwise({
redirectTo: '/'
});
});
var ProductController = function($scope, $log, $routeParams, $location) {
var saveProduct = function() {
// how to get data from either TypeAController or TypeBController here when saved from ProductController
$log.log('Product saved')
$location.path('/');
};
$scope.saveProduct = saveProduct;
// values
$scope.ProductId = 1001;
$scope.Name = 'Product 1001';
$scope.Type = 'typea';
};
app.controller("ProductController", ProductController);
}());
And this is one of the dynamic views with it's controller:
<div ng-controller='TypeAController'>
<h1>Type A Settings</h1>
<fieldset>
<div class="dnnFormItem">
<label>Width:</label>
<br />
<input type="text" name="width" ng-model="Width" />
<br />
<label>Height:</label>
<br />
<input type="text" name="height" ng-model="Height" />
<br />
</div>
</fieldset>
</div>
<script>
console.log('TypeA is loaded');
var TypeAController = function($scope, $log) {
};
angular.module('ProductApp').controller("TypeAController", TypeAController);
</script>
I ended up doing a simple factory to share an object between controllers as demonstrated in this plunk, http://plnkr.co/edit/6kQYKU. This also demonstrates loading a dynamic view based on a field in the parent view.
var stateService = function() {
'use strict';
var state = {};
return {
state: state,
};
};
app.factory('StateService', stateService);
I'm developing a simple todo app with Angular and Firebase using AngularFire module.
So I have a boolean attribute in my model represented by a checkbox in the template, the problem is that I'm trying to use the three way data binding from AngularFire using the $bind method to keep the all changes syncronized (firebase data, DOM and ng-model) but the firebase data is not updating when I select a checkbox.
Here's my controller where I'm using the AngularFire $bind method:
angular.module('singularPracticeApp')
.controller('TodoCtrl', ['$scope', 'TodoService', function ($scope, todoService) {
$scope.todos = todoService;
$scope.todos.$bind($scope, 'todo.done');
$scope.addTodo = function () {
$scope.todos.$add({text: $scope.todoText, done:false});
$scope.todoText = '';
};
$scope.remaining = function () {
var count = -11;
angular.forEach($scope.todos, function(todo){
count += todo.done? 0 : 1;
});
return count;
};
$scope.clear = function (id) {
$scope.todos.$remove(id);
};
}]);
And here is the tempalte file:
<div ng-controller="TodoCtrl">
<h4>Task runner</h4>
<span>{{remaining()}} todos left.</span>
<ul>
<li ng-repeat="(id, todo) in todos">
<input type="checkbox" ng-model="todo.done">
<span ng-if="todo.done" style="color: #ddd;">{{todo.text}}</span>
<span ng-if="todo.done == false">{{todo.text}}</span>
<small ng-if="todo.done">clear</small>
</li>
</ul>
<form ng-submit="addTodo()">
<input type="text" ng-model="todoText" placeholder="New todo item">
<input type="submit" class="btn btn-primary" value="add">
</form>
</div>
Am I missing something? Is really possible to make this work with a simple checkbox?
Thanks in advance.
You haven't included todoService here so it's going to be difficult to give you an accurate answer. I'll assume that todoService returns a $firebase instance containing the todos since that seems likely. Keep in mind that the problem could be in that code as well.
Several problems you can address, which may resolve your issue:
Your TodoCtrl is not per-item
You seem to be using TodoCtrl as if it were created per-item in the ng-repeat. However, it exists outside the scope of ng-repeat and is only created once for the entire list.
Ng-repeat does not re-use your existing controller scope.
Directives operate in an isolate scope. That means that they do not share scope with your controller. So when you do ng-repeat="todo in todos" you do not add todo into your controller's scope.
This makes sense since each ng-repeat iteration would overwrite the same todo object.
You are trying to double-bind to a synchronized object
You are trying to create a three-way binding $scope.todos.[$todo].done, but you have already created a three-way binding on $scope.todos. Instead, let $scope.todos take care of synchronization.
You've attempted to bind $scope.todos to a property in itself
When you call $bind, you are binding $scope.todos to $scope.todos.todo.done. Obviously this self-referential statement isn't what you intended. I can't tell what is returned by your service but maybe you meant this:
todoService.$bind($scope, 'todos');
If you don't want to automatically push changes on the entire todos list, you can add a $save call instead of using $bind:
$scope.todos = todoService;
<input type="checkbox" ng-model="todo.done" ng-change="$parent.todos.$save(id)">
All together:
angular.module('singularPracticeApp')
.service('todoService', function($firebase) {
return $firebase( new Firebase(URL_TO_TODOS_LIST) );
});
.controller('TodoCtrl', function($scope, todoService) {
todoService.$bind($scope, 'todos');
$scope.addTodo = function () {
$scope.todos.$add({text: $scope.todoText, done:false});
$scope.todoText = '';
};
/** ... and so on ... **/
});