Independent scopes for similar controllers in Angular - angularjs

I'm working on an app the uses multiple (but similar) controllers (controllerA) on a page and another controller (controllerB) that could change some properties, but ONLY if the element of controllerA was previously selected.
(in controllerA some properties could have default values, that are passed and applied by using data- attributes)
In this stage, I can only modify the last color property with controllerB (which makes sense, because that's the active scope. My question is, how can I change controllerB scope to be the "active" scope-copy of the selected controllerA?
// Code goes here
var webApp = angular.module("webApp", []);
webApp.controller("controllerA", function($scope, $rootScope, $element, styleFactory){
$scope.selected = false;
var color = angular.element($element[0]).attr("data-style-color");
styleFactory.setColor(color);
$scope.data = styleFactory.getData();
$scope.select = function(){
$scope.selected = !$scope.selected;
}
});
webApp.controller("controllerB", function($scope, $rootScope, $element, styleFactory){
$scope.data = styleFactory.getData();
});
webApp.factory("styleFactory", function(){
var data = {"style":"color:yellow"}
return {
setColor: function(color){
data = {"style":"color:"+color};
},
getData: function(){
return data;
}
};
});
for full example:
http://plnkr.co/edit/He2m7ArfRSur9Igq2eur?p=preview
thanks,
--iM

You can see a modified plnkr that works as you described here.
Personally, I would restructure the architecture of the app and rethink the styleFactory implementation.
Some points for thought:
data = {"style":"color:"+color} replaces the whole data object, thus $scope.data = styleFactory.getData() works only for the last controllerA as the reference is lost for all others.
Try creating a parent controller that will be on top of controllerA and controllerB.
Try solving this problem using directives.

Related

How should I use Angular services with factories for code reuse?

I have some Angular Controllers where there's always the need to store an array of items, push elements to it and make it accessible.
I thought then I could have a ServicesModule with a factory offering such service.
So I got this service declaration:
angular.module('ServicesModule',[]);
angular.module('ServicesModule')
.factory('newOrdersService', function($rootScope,$http) {
var newOrder = [];
return {
getOrder: function() {return newOrder;},
addItem: function(item) {newOrder.push(item);}
};
});
I got this controller example where I use the service:
angular.module('app',['ServicesModule']);
angular.module('app').
controller('MainController', function($scope, $http, newOrdersService){
$scope.order = newOrdersService.getOrder();
$scope.addItem = newOrdersService.addItem();
});
However I can't get it working, seems like using either getOrder or addItem from the service won't make any effect.
What am I doing wrong?
I offer a plunkr example:
I forked your plunker.
You can bind your scope variables directly to a function.
$scope.addItem = newOrdersService.addItem;
Alternately:
$scope.addItem = function(item){
newOrdersService.addItem(item);
}
$scope.addItem = newOrdersService.addItem() doesn't work as this will only run your addItem once when the controller is loaded.
And when you add a new item, it's important that you create a new object, or else the objects inside your array will continue being bound to the ng-models (and changed when you enter something into the text inputs).

Modifying factory value in directive Angular

I have the following code : http://codepen.io/Andarius/pen/Ggryge .
When the user draws a 'crop_area', the crop button should not be disabled anymore.
Why is the value no_crop_area (from the Image factory) not updated when drawing ?
Is it a scope problem ?
Also, I'm pretty new to AngularJS and was wondering what is the best practice when passing a factory to a controller (if there is one)
Given a factory :
myApp.factory('myFactory', function () {
return {foo:{bar:2}};
});
Is it better to do :
myApp.controller('myCtrl', ['myFactory',function (myFactory) {
var self = this;
self.foo = myFactory;
self.bar = myFactory.bar;
}]);
or
myApp.controller('myCtrl', ['myFactory',function (myFactory) {
var self = this;
self.foo = myFactory;
self.bar = self.foo.bar;
}]);
I have forked your code. Here is a working one.
http://codepen.io/anon/pen/qERpew?editors=101
The button is not getting enabled because, you are doing all the processing related to creating crop area by using javascript event handlers, so the code related to $scope (ie.angular related), will not come into effect. To have them in effect, you have to wrap the code related to $scope into $scope.$apply(function(){ // Your $scope variable update code.}).
PS: As per convention you should not use $scope name inside directive.Instead of that use scope.

how can I access a variable defined in one controller from the scope of another controller?

I have the following controllers:
HeaderCtrl, NewsFeedCtrl, MainCtrl
MainCtrl contains both the other two controllers, which are in the same level.
I'm defining an object in authenticationService and update its value in MainCtrl and I update it frequently in NewsFeedCtrl and I want to display its value in the HTML page controlled by HeaderCtrl.
when I use this line in my HeaderCtrl:
$scope.unreadNum=authenticationService.notificationList.length;
and then I use data binding in my HTML page to display its value:
{{unreadNum}}
I only get the initial value I inserted in authenticationService, not the one after the update in the other controllers.
it seems that my HeaderCtrl is defining all his scope objects only one time and then there's no more use for the Ctrl, but I still want his HTML page to be updated after the update in object values in other controllers.
to sum it up: the value of the object I want is stored in one of my services, and I am unable to display it in my HTML page because I can't seem bind it correctly.
You can send messages between the controllers using a service. The service looks something like this...
aModule.factory('messageService', function ($rootScope) {
var sharedService = {};
sharedService.message = {};
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();
};
sharedService.broadcastItem = function () {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
In the controller that is sending the message, inject this service...
aModule.controller("sendingController", function ($scope, messageService) {
Then add a method that will broadcast the change to any controller that is listening...
$scope.sendMessage = function (someObject) {
messageService.prepForBroadcast(someObject);
},
In any controller that wants to receive the message, inject the service, and add a handler like this to do something with the message...
$scope.$on('handleBroadcast', function() {
//update what you will..
$scope.something = messageService.message;
});

Preserve state with Angular UI-Router

I have an app with a ng-view that sends emails to contact selected from a contact list.
When the users select "Recipient" it shows another view/page where he can search, filter, etc. "Send email" and "Contact list" are different html partials that are loaded in the ng-view.
I need to keep the send form state so when the users select someone from the Contact List it returns to the same point (and same state). I read about different solutions ($rootScope, hidden divs using ng-show, ...) but I want to know if UI-router will help me with it's State Manager. If not, are there other ready-to-use solutions?
Thanks!
The solution i have gone with is using services as my data/model storage. they persist across controller changes.
example
the user service ( our model that persists across controller changes )
app.factory('userModel', [function () {
return {
model: {
name: '',
email: ''
}
};
}]);
using it in a controller
function userCtrl($scope, userModel) {
$scope.user = userModel;
}
the other advantage of this is that you can reuse your model in other controllers just as easly.
I'm not sure if this is recommended or not, but I created a StateService to save/load properties from my controllers' scopes. It looks like this:
(function(){
'use strict';
angular.module('app').service('StateService', function(){
var _states = {};
var _save = function(name, scope, fields){
if(!_states[name])
_states[name] = {};
for(var i=0; i<fields.length; i++){
_states[name][fields[i]] = scope[fields[i]];
}
}
var _load = function(name, scope, fields){
if(!_states[name])
return scope;
for(var i=0; i<fields.length; i++){
if(typeof _states[name][fields[i]] !== 'undefined')
scope[fields[i]] = _states[name][fields[i]];
}
return scope;
}
// ===== Return exposed functions ===== //
return({
save: _save,
load: _load
});
});
})();
To use it, I put some code at the end of my controller like this:
angular.module('app').controller('ExampleCtrl', ['$scope', 'StateService', function ($scope, StateService) {
$scope.keyword = '';
$scope.people = [];
...
var saveStateFields = ['keyword','people'];
$scope = StateService.load('ExampleCtrl', $scope, saveStateFields);
$scope.$on('$destroy', function() {
StateService.save('ExampleCtrl', $scope, saveStateFields);
});
}]);
I have found Angular-Multi-View to be a godsend for this scenario. It lets you preserve state in one view while other views are handling the route. It also lets multiple views handle the same route.
You can do this with UI-Router but you'll need to nest the views which IMHO can get ugly.

How to reuse one controller for 2 different views?

I have defined one controller, and apply it to 2 views with small differences.
Angular code:
app.controller('MyCtrl', function($scope) {
$scope.canSave = false;
$scope.demo = {
files : [{
filename: 'aaa.html',
source: '<div>aaa</div>'
}, {
filename: 'bbb.html',
source: '<div>bbb</div>'
}]
}
$scope.newFile = function(file) {
$scope.demo.files.push(file);
}
$scope.$watch("demo.files", function(val) {
$scope.canSave = true;
}, true);
});
View 1:
<div ng-controller="MyCtrl"></div>
View 2:
<div ng-controller="MyCtrl"></div>
The sample code is very simple, but there are a lot of code and logic in my real project.
The View 1 and 2 have almost the same features, only with a few differences, but I do need to write some code for each of them in the controller.
I don't want to create 2 different controllers for them, because they have most of same logic. I don't want to move the logic to a service to share it between the 2 controllers, because the logic is not that common to be a service.
Is there any other way to do it?
Under the given conditions I might be doing something like
function MyCommonCtrl(type){
return function($scope, $http) {
$scope.x = 5;
if(type = 't1'){
$scope.domore = function(){
}
}
....
....
}
}
angular.module('ng').controller('Type1Ctrl', ['$scope', '$http', MyCommonCtrl('t1')]);
angular.module('ng').controller('Type2Ctrl', ['$scope', '$http', MyCommonCtrl('t2')]);
Then
<div ng-controller="Type1Ctrl"></div>
and
<div ng-controller="Type2Ctrl"></div>
I don't know your specific set-up but your 2 controllers could inherit from a common ancestor.
Type1Ctrl.prototype = new MyCtrl();
Type1Ctrl.prototype.constructor = Type1Ctrl;
function Type1Ctrl() {
// constructor stuff goes here
}
Type1Ctrl.prototype.setScope = function() {
// setScope
};
Type2Ctrl.prototype = new MyCtrl();
Type2Ctrl.prototype.constructor = Type2Ctrl;
function Type2Ctrl() {
// constructor stuff goes here
}
Type2Ctrl.prototype.setScope = function() {
// setScope
};
I also faced similar problem and scope inheritance solved my problem.
I wanted to "reuse" a controller to inherit common state/model ($scope) and functionality (controller functions attached to $scope)
As described in the "Scope Inheritance Example" I attach parent controller to an outer DOM element and child controller to the inner. Scope and functions of parent controller "merge" seamlessly into the child one.
Here is another option. Slightly modified from this blog post
app.factory('ParentCtrl',function(){
$scope.parentVar = 'I am from the parent'
};
});
app.controller('ChildCtrl', function($scope, $injector, ParentCtrl) {
$injector.invoke(ParentCtrl, this, {$scope: $scope});
});
here is a plunker

Resources