Sharing data between controllers - angularjs

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.

Related

AngularJS Multiple views with shared logic design pattern

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).

Is it acceptable to load controllers in the route AND body?

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>

Dynamically loading controllers with their own scopes

I'm having a problem which I'm not sure whether is a down to a limitation of Angular (possibly) or a limitation of my knowledge of Angular (probably).
I am trying to take an array of controllers, and dynamically create/load them. I have a prototype working to the point where the controllers run and the root scope can be accessed, but I cannot dynamically attach ng-controller to divs in order to encapsulate the controllers into their own local scopes.
The problem is that the templates are bound to the root scope but not to their own scopes.
My example will hopefully explain my quandary better.
JSFiddle: http://jsfiddle.net/PT5BG/22/ (last update 16:30 BST)
It may not make sense why I am doing it this way, but I have pulled this concept out of a larger system I am creating. In case you have other suggestions, these are the laws by which I am bound:
Controllers cannot be hard-coded, they must be built from an array
Scopes cannot be shared between controllers, they must have their own scopes
The docs on AngularJS are not exactly comprehensive so I'm hoping someone here can help!
You can just pass the controller name through and use the $controller service and pass the locals through to it. You'll need some sort of ModuleCtrl thing to co-ordinate all this. Here is a basic example that does what you want.
http://jsfiddle.net/PT5BG/62/
angular.module('app', [])
.controller('AppCtrl', function ($scope, $controller) {
$scope.modules = [
{ name: "Foo", controller: "FooCtrl" },
{ name: "Bar", controller: "BarCtrl" }]
})
.controller('ModuleCtrl', function ($scope, $rootScope, $controller) {
$controller($scope.module.controller, { $rootScope: $rootScope, $scope: $scope });
})
.controller('FooCtrl', function ($rootScope, $scope) {
$rootScope.rootMessage = "I am foo";
$scope.localMessage = "I am foo";
console.log("Foo here");
})
.controller('BarCtrl', function ($rootScope, $scope) {
$rootScope.rootMessage = "I am bar";
$scope.localMessage = "I am bar";
console.log("Bar here");
});
The way I finally got around this was quite simple, it was just a case of working it out.
So I have a list of modules, that I get from an API, and I want to instantiate them. I include the template file by building the path via convention, like so:
<!-- the ng-repeat part of the code -->
<div ng-repeat="module in modules">
<ng-include src="module.name + '.tpl.html'"></ng-include>
</div>
In each of the modules template files, I then declare the ng-controller and I declare a method to fire in ng-init. As the template is still within the ng-repeat loop, it has access to module, which has the data we want to pass to the child controller. ng-init runs on the local scope, so we pass in the module object:
<!-- the template of the module -->
<div ng-controller="ModuleCtrl" ng-init="init(module)">
...
</div>
And then we store it on the local scope and there you go, injected the object.
/* the controller of the module */
.controller('ModuleCtrl', function ($scope) {
$scope.init = function(module) {
this.module = module;
};
// this.module is now available inside the controller
});
It took a bit of hacking but it works perfectly for now.

How to load language-specific templates in AngularJS?

I have a route defined as
$routeProvider.when('/:culture/:gameKey/:gameId/closed', { templateUrl: '/templates/tradingclosed', controller: TradingClosedCtrl });
I would like angular to include the "culture" parameter when requesting the template somehow, so I can serve a translated template.
Is this possible?
If I'm reading this correctly you'd like to somehow use the culture parameter from the url route to determine which location to retrieve your template.
There may be a better way but this post describes retrieving the $routeParams inside a centralized controller with ng-include to dynamically load a view.
Something similar to this:
angular.module('myApp', []).
config(function ($routeProvider) {
$routeProvider.when('/:culture/:gameKey/:gameId/closed', {
templateUrl: '/templates/nav/urlRouter.html',
controller: 'RouteController'
});
});
function RouteController($scope, $routeParams) {
$scope.templateUrl = 'templates/tradingclosed/' + $routeParams.culture + '_template.html';
}
With this as your urlRouter.html:
<div ng-include src="templateUrl"></div>
You can define the controller you want to load in your views using ng-controller and access the $routeParams for the additional route parameters:
<div ng-controller="TradingClosedCtrl">
</div>
I've posted similar question with working Plnkr example of solution like #Gloopy suggested.
The reason why you can't implement that without ng-include is that routing is done in 'configuration' block, where you can't inject any values (you can read about these blocks in Modules documentation, section Module Loading & Dependencies
If you want not to introduce new scope, you can replace ng-include with my stripped version of ng-include directive, that do absolutely same that ng-include does, but do not create new scope: source of rawInclude directive
Hope that solution will satisfy your use case.

How to include one partial into other without creating a new scope?

I've this routes.
// index.html
<div ng-controller="mainCtrl">
<a href='#/one'>One</a>
<a href='#/two'>Two</a>
</div>​​​​​​​​​
<div ng-view></div>
And this is how I'm loading the partials into my ng-view.
// app.js
​var App = angular.module('app', []);​​​​​​​
App.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/one', {template: 'partials/one.html', controller: App.oneCtrl});
$routeProvider.when('/two', {template: 'partials/two.html', controller: App.twoCtrl});
}]);
When I click the links, it shows me the appropriate markup inside the ng-view. But when I try to include partials/two.html inside partials/one.html using ng-include, it shows it properly but creates a different scope so I'm not able to interact with it.
// partials/two.html - markup
<div ng-controller="twoCtrl">I'm a heading of Two</div>
// partials/one.html - markup
<div ng-controller="oneCtrl">I'm a heading of One</div>
<div ng-include src="'partials/two.html'"></div>
​
How do I resolve this problem? Or Is there any other way to achieve the same result?
You can write your own include directive that does not create a new scope. For example:
MyDirectives.directive('staticInclude', function($http, $templateCache, $compile) {
return function(scope, element, attrs) {
var templatePath = attrs.staticInclude;
$http.get(templatePath, { cache: $templateCache }).success(function(response) {
var contents = element.html(response).contents();
$compile(contents)(scope);
});
};
});
You can use this like:
<div static-include="my/file.html"></div>
The documentation for ngInclude states "This directive creates new scope." so this is by design.
Depending on the type of interaction you are looking for you may want to take a look at this post for one way to share data/functionality between the two controllers via a custom service.
So this isn't an answer to this question but i made it here looking for something similar and hopefully this will help others.
This directive will include a partial without creating a new scope. For an example you can create a form in the partial and control that form from the parent controller.
Here is a link to the Repo that i created for it.
good luck :-)
-James Harrington
You can actually do this without using a shared service. $scope.$emit(...) can dispatch events to the $rootScope, which can listen for them and rebroadcast to the child scopes.
Demo: http://jsfiddle.net/VxafF/
Reference:
http://www.youtube.com/watch?v=1OALSkJGsRw (see the first comment)

Resources