In my web application i start feeling the need to pass objects from one page controller to another, not just parameters in the route. I think that this can make my application faster and the code clean. By page controllers i mean controllers corresponding to $routeProvider routes.
I think i can easily implement this with a service, say pageBroker. This service will take a route and an object, and load the page corresponding to the route:
pageBroker.load('/assign-task/', { task: { ... } });
That page would find the object in a special section of the service:
pageBroker.pageData.task;
Page data can be reset for every route change.
This is not an implementation problem, but a software design problem. Usually similar questions get closed on Stack overflow, but i will try once again: is this a good or a bad idea? Is it suitable for the Angular architecture? Why yes or why not?
From my experience with AngularJS, I would say using Services is a good approach, always worked well for me. (But if anyone thinks differently, I will be more than happy to hear your thoughts on this)
var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return {message: "I'm data from a service and I can be shared between multiple controllers"}
})
function FirstCtrl($scope, Data){
$scope.data = Data;
}
function SecondCtrl($scope, Data){
$scope.data = Data;
}
and in your view you can use it like
<div ng-app="myApp">
<div ng-controller="FirstCtrl">
<input type="text" ng-model="data.message">
<h1>{{data.message}}</h1>
</div>
<div ng-controller="SecondCtrl">
<input type="text" ng-model="data.message">
<h1>{{data.message}}</h1>
</div>
</div>
You can even bind the Data.message to a model in the controller scope and whenever you change the model, the data-binding will change also on the Service and consequently on the other Controller (But sometimes you might need to call $apply).
This video might be useful to you: https://egghead.io/lessons/angularjs-sharing-data-between-controllers
Related
I'm using ui-router and my layout is divided into 2 ui-views, side-bar and main content
the side-bar offers options that changes the main content model (altering values, setting filters) and that's a problem because as far as I understand they can never share same controller (instance)
at this point there are two solutions I'm considering,
1. Working with one view moving the sidebar into the main view that way they will reside within a single controller instance, It's a bit ugly but still a solution
2. communicate between controllers with a messaging, invoking whatever needed in that disconnected matter
I don't like neither of those solutions, I'll be happy to get your design proposals
current routing def example (mind that same layout is common for my application and in used repeatedly:
$stateProvider.state('home', {
url: "/home",
views: {
main: {
templateUrl:"homeTemplate.html",
controller: "HomeController"
},
sidebar: {templateUrl: "homeSidebarTemplate.html"}
}
})
Take a look at angular services
You can use them for these purposes. A service is a singleton in which you can share data between any module. Put your data into the service and use it as a model in your main view and side bar.
For example:
angular.module('myApp.someNamespace', [])
.factory('someService', function () {
var service = {};
service.myList = {1,2,3};
service.myMenu = {'apples', 'oranges', 'pears'}
service.addToList = function(number) {
service.myList.push(number);
}
return service;
});
inject this service into your controller and sidebar directive:
angular.module('myApp.myControllers', [])
.controller('myCtrl', function ($scope, someService) {
$scope.model = someService;
});
In your view bind to your service variables/methods:
<div ng-repeat="number in model.myList">
<input type="number" ng-model="number"/>
</div>
<input type="number" ng-model="newNumber"/>
<button ng-click="model.addToList(newNumber)">Add</button>
Another advantage of using this pattern is that your views will stay where you were when navigating back and forth (more stateful), because it gets the data from your singleton service. Also you'd only need to get the data from your api once (until you refresh your browser ofcourse).
Most articles demonstrate one method or the other... specifying the controller in the route OR the body. While I realize specifying the controller in the route provides additional benefits WHEN NEEDED (pre-loading required view resources, etc.), it is illogical to think that a modular application will be able to (or SHOULD) handle all of the functionality for a complex view.
Any proven examples (links) showing a combined approach would really help ease my mind.
Thanks, in advance.
No.
You may use both, but not for the same instance of a controller.
As an example, you can use ng-controller for your page menu controller and define the page controller loaded into your ui-view in the state config. This is fine as long as the page view does not have an ng-controller the same controller.
Your controllers constructor will run twice if you invoke it twice.
Added Snippet to Demonstrate
So in the following code we bring in the controller IndexCtrl in the uiRouter state config and in the index.html view. As you can see if you run this snippet, you have two instances of the IndexCtrl running.
Why is this a problem?
It is not a problem if this is your intention. It becomes a problem if this is your intention, to have two instances of the controller. If you follow this pattern unknowingly and create an application you will have an unneeded instance of every controller.
This will also cause developer confusion. How come I can't get back the value of a property in a controller? You will see bugs like this because a developer will set a property on one instance of a controller and then expect the same value to be in the other instance.
Controllers are classes. They are constructor functions -- so you can have multiple instances of them. And there are valid use cases to have multiple instances of a controller. But you shouldn't accidentally have two instances.
angular.module('app', ['ui.router'])
.config(['$stateProvider',
function($stateProvider) {
$stateProvider.state('index', {
url: '*path',
controller: "IndexCtrl as vm1",
templateUrl: 'index.html'
});
}
])
.controller('IndexCtrl', IndexCtrl);
function IndexCtrl() {
this.value = 0;
}
IndexCtrl.prototype.add = function() {
this.value += 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js"></script>
<div ng-app="app">
<ui-view></ui-view>
<script type="text/ng-template" id="index.html">
<div ng-controller="IndexCtrl as vm2">
Controller1 {{vm1.value}} Controller2 {{vm2.value}}
<button ng-click="vm1.add()">Add Ctrl1</button>
<button ng-click="vm2.add()">Add Ctrl2</button>
</div>
</script>
</div>
I am creating an AngularJS application that is a web planner that consists of 6 steps. Each step has its own view and its own controller. There is some data that should be accessible from all states (There is Order level information that is used in the header/title of the planner). I have put this information in a service, but the problem is in this root controller, it does not have access to the $stateParams which is how I know whether or not API data needs to be fetched.
I could use $scope.$emit() or $scope.$broadcast to tell my root controller when ui.router runs and finds the ID in the URL, but that just seems like bad design. What is the best way to make the $stateParams available to a global controller?
Here is what the HTML looks like.
<div ng-app="MyApp.SubModule.Module" ng-controller="RootController">
<sk-overlay ng-show="overlayVisible"></sk-overlay>
<h2 class="dashTitle"> {{ (Order.CustomerInfo.CompanyName ? '- ' + Order.CustomerInfo.CompanyName : '') }}</h2>
<div id="wpMenuContainer" class="wpMenuContainer">
<ul class="wpMenu clearfix">
<li ng-repeat="chevron in Chevrons" ng-class="{ active : chevron.active, navable : $index < Index }" ng-click="!($index < Index)||navigate(chevron, $index)">
<span>{{ chevron.name }}</span>
</li>
</ul>
</div>
<div ui-view class="do"></div>
</div>
I want RootController to be used to control data that is global to the entire application, but that state does not exist in the $stateProvider, therefore it does not get access to the $stateParams variables.
You could always use a shared service (I think I actually mean factory here) to enable two way communication between different controlers.
This is a hot topic and there are lots of related questions on this:
Share data between AngularJS controllers
Passing data between controllers in Angular JS?
Update
You could actually make that factory global by assigning it to the $rootScope, thus being able to observe it without having to assign it in every controller:
angular.module('app')
.factory('sharedData', function() {
return {};
})
.controller('mainController', function($rootScope, sharedData) {
$rootScope.sharedData = sharedData;
$rootScope.$watch('sharedData', updateMyStuff, true);
})
.controller('subController', function(sharedData) {
sharedData.stateParams = 'inaccessible stuff';
});
$stateParams can be injected into any of your controllers/services/directives using DI, it is a services that is provided by ui-router.
$state can also be injected into any of your controllers/services/directives using DI, it is a services that is provided by ui-router. You can access stateParams through $state via $state.params
Also, ui-router mentions that it is handy to set up $state in $rootScope on run() so that it can be access primarily in templates.
I am making first steps in Angular.JS and faced the problem when variable available in scope is no longer available in router view. Setup is as follows:
var vApp = angular.module('appG', ['ngRoute', 'appG.directives'])
.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/', {templateUrl: '/partials/form.html', controller: 'ctrlMain'})
.when('/welcome', {templateUrl: '/partials/welcome.html', controller: 'ctrlMain'})
.otherwise({redirectTo: '/'});
});
validationApp.controller('ctrlMain', function($scope, $http, $location) {
$scope.user = {};
$scope.submitForm = function () {
$http.post('/signup', $scope.user).
success(function(data) {
$location.url('/welcome');
});
};
$scope.submitData = function () {
if ($scope.signupForm.$valid) {
$scope.submitForm();
} else {
$scope.signupForm.submitted = true;
}
};
});
HTML code:
<html ng-app="appG">
<div ng-controller="ctrlMain">
<ng-view></ng-view>
</div>
</html>
form.htm (just part of it):
<form name="signupForm" id="signupForm" ng-submit="submitForm()" novalidate>
<input type="text" class="text" ng-class="{'submit-error' : signupForm.submitted}" name="uFirst" placeholder="First name" ng-model="user.first" required/>
<i class="fa fa-sign-in pull-left"></i>Submit
</form>
welcome.html:
<p>{{user.first}}, thank you! </p>
Partial form.html contains form code with basic validation and works ok, partial welcome.html contain simple with texts containing {{user.first}} (user has binding from form and name is a property of the user object. The problem is that {{user.first}} is always empty in welcome.html partial. I would appreciate any help to figure out how it is possible to pass value of the $scope.user to the second partial. Thank you!
New scopes are created whenever you define a route/controller. Your two controllers have completely seperate $scope objects. In this case they're called "sibling" scopes. A variable defined in ctrlMain won't be available in welcome even though you use the same controller reference. The router is actually creating a fresh instance of ctrlMain.
The quick easy fix is to inject $rootScope and set $rootScope.user instead of $scope.user. $rootScope is a special scope that is the parent of all scopes. The values will be "inherited" by all child scopes in your entire app.
Obviously that's not that efficient and usually frowned upon, so the proper way is usually to use a service. You'll have to read up on creating a service, but you'd then inject the service and set your user object there. In fact, your HTTP stuff would go into the service as well.
An option that I often use, which follows the same pattern as most server side MVC frameworks (like Rails) is to create a controller called 'ApplicatonController' , and set that on your body tag, with ng-controller="ApplicationController"
Your MainCtrl will load inside the body tag, so it will inherit anything found in the parent controller.
It's called controller nesting.
You create a controller called "ApplicationController" as you have your MainCtrl, except now you can access properties and methods from the application controller from within the MainCtrl without the need to inject anything additional like $rootScope.
The hierarchy now goes
$rootScope -> ApplicationController -> MainCtrl
I find this approach is easier to wrap your head around if you're familiar with frameworks like Rails that follow the same structure.
I also like to avoid over confusing things by creating a service for everything, because you end up with huge dependency injection strings which become a hassle to manage.
Controller nesting is probably not going to be the most "Angular" way of doing it, but it solves your problem with minimal effort.
I guess it is possible to have many angular-modules attached to different regions within one shellpage.
But can modules in AngularJS "talk" to each other?
If yes, how?
There are various ways module can interact or share information
A module can be injected into another module, in which case the container module has access to all elements of the injected module. If you look at angular seed project, modules are created for directive, controllers, filters etc, something like this
angular.module("myApp", ["myApp.filters", "myApp.services", "myApp.directives", "myApp.controllers"])
This is more of a re usability mechanism rather than communication mechanism.
The second option is as explained by #Eduard would be to use services. Since services are singleton and can be injected into any controller, they can act as a communication mechanism.
As #Eduard again pointed out the third option is to use parent controller using $scope object as it is available to all child controllers.
You can also inject $rootScope into controllers that need to interact and use the $broadcast and $on methods to create a service bus pattern where controllers interact using pub\sub mechanism.
I would lean towards 4th option. See some more details here too What's the correct way to communicate between controllers in AngularJS?
Using the service mechanism to communicate among module's controllers.
(function () {
'use strict';
//adding moduleB as dependency to moduleA
angular.module('Myapp.moduleA', ['Myapp.moduleB'])
.controller('FCtrl', FCtrl)
.service('sharedData', SharedData);
//adding the dependency shareData to FCtrl
FCtrl.$inject = ['sharedData'];
function FCtrl(sharedData) {
var vm = this;
vm.data = sharedData.data;
}
//shared data service
function SharedData() {
this.data = {
value: 'my shared data'
}
}
//second module
angular.module('Myapp.moduleB', [])
.controller('SCtrl', SCtrl);
SCtrl.$inject = ['sharedData'];
function SCtrl(sharedData) {
var vm = this;
vm.data = sharedData.data;
}
})();
And the HTML as follows:
<html ng-app="firstModule">
<body>
<div ng-controller="FCtrl as xyz">
<input type=text ng-model="xyz.data.value" />
</div>
<div ng-controller="SCtrl as abc">
<input type=text ng-model="abc.data.value" />
</div>
</body>
</html>
You can use services and controllers inheritance (explained here http://docs.angularjs.org/guide/dev_guide.mvc.understanding_controller)
in any case, you shuold consider not having your controllers tighlty coupled.