Sharing scope in different views with custom controllers - javascript

I've got the next problem and I'd like to find a solution or if I should even be doing this like I am.
I have the following ui-router configuration:
$stateProvider.state('ListCounterparties', {
url:"/counterparties",
data: { NavMenuItem : 'Counterparties / Desks' },
views: {
'' : {
templateUrl:"./app/module_Counterparties/templates/ListCounterparties.html",
controller:"CounterpartiesControllerCtrl"
},
'deskLists#ListCounterparties' : {
templateUrl : './app/module_Counterparties/templates/DesksDualListTemplate.html',
controller:'DesksControllerCtrl'
}
}
The first, unnamed view, has a table from which I can select a row and then a method will be called to populate a dual list from the second view.
Up until now, I've had both in the same controller, but the controller is getting too big and I decided I had to separate them.
The method to populate the dual lists in 'deskLists#ListCounterparties' is defined in DesksControllerCtrl but it should be called in CounterpartiesControllerCtrl, as the event of row selection is in that controller.
The problem is that the scopes are not shared and the method is inaccesible to the unnamed view.
Accessing the scope in DesksControllerCtrl I could see that accessing the $parent property twice I can get to the CounterpartiesControllerCtrl, but I don't thin that's an ideal thing to do.
Thanks in advance.

Sharing data, methods, etc. between multiple controllers the Angular way would be to create service(s). That means, you create e.g. a service which holds all your data and another one which provides functionality for several controllers. Then, just inject them in your controllers:
var myApp = angular.module('myApp', []);
myApp.factory('myGlobalData', function() {
return {
data: 1
};
});
myApp.factory('myService', function(myGlobalData) {
return {
increment: function() {
myGlobalData.data++;
}
};
});
myApp.controller('MyCtrlA', function($scope, myGlobalData, myService) {
$scope.myGlobalData = myGlobalData;
$scope.myService = myService;
});
myApp.controller('MyCtrlB', function($scope, myGlobalData, myService) {
$scope.myGlobalData = myGlobalData;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<p ng-controller="MyCtrlA">
{{myGlobalData.data}}
</p>
<p ng-controller="MyCtrlB">
{{myGlobalData.data}}
</p>
<div ng-controller="MyCtrlA">
<button ng-click="myService.increment()">Increment data in myGlobalData service</button>
</div>
</div>

Related

Defining angular controllers and component

I'm trying to call a function from within one angular component's controller to another component's controller. I found this answer, but the controllers are defined differently than what is shown in the tutorials I followed, so I'm confused as to how I'm supposed to reference the components and templates.
Here is how I currently define one of my controllers based on the official angular tutorial
angular.
module('moduleName').
component('firstComponent', {
templateUrl: 'url/to/template',
controller: function FirstController($scope) {
//I want to call a function in SecondController here
}
});
//in another component file
angular.
module('moduleName').
component('secondComponent', {
templateUrl: 'url/to/template',
controller: function SecondController($scope) {
//function here which will be called
}
});
Say I re-structure them like in the example I linked above...
var app= angular.module("myApp",[]);
app.controller('One', ['$scope', '$rootScope'
function($scope) {
$rootScope.$on("CallParentMethod", function(){
$scope.parentmethod();
});
$scope.parentmethod = function() {
// task
}
}
]);
//in another file
var app= angular.module("myApp",[]);
app.controller('two', ['$scope', '$rootScope'
function($scope) {
$scope.childmethod = function() {
$rootScope.$emit("CallParentMethod", {});
}
}
]);
How am I supposed to reference each controller's component which they belong to, and their respective templates? I tried to write them like in the example I linked above, but nothing happened. I didn't get any errors, but literally nothing happened. Nothing was displayed on the page. It tried to write to the console, but nothing appeared.
Your second block of code has the right concept, but both controllers need to be instantiated for it to work.
Here's a working JSFiddle. https://jsfiddle.net/reid_horton/rctx6o1g/
When you click the button, the text Hello from callerComponent! appears below it.
HTML
<div ng-app="myApp">
<caller-component></caller-component>
<callee-component><</callee-component>
</div>
JS
var app = angular.module("myApp", []);
app.component("callerComponent", {
template: "<button ng-click='externalCall()'>Click Me!</button>",
controller: function ($scope, $rootScope) {
$scope.externalCall = function () {
$rootScope.$emit("setText");
}
}
});
app.component("calleeComponent", {
template: "<p>{{ myText }}</p>",
controller: function ($scope, $rootScope) {
$rootScope.$on("setText", function () {
$scope.myText = "Hello from callerComponent!"
});
}
});

Should I pass data between directive and service?

I have a page with a controller that has a directive that displays a form with inputs.
The directive has an object that contains all the inputs (and their ng-model) that are displayed on the form. This binds the form inputs data to the object variable inside the directive.
I need to display results (and other actions) submiting the data of the form.
For that I created a service that handles the business logic and thae ajax calls.
The questions here is... how should I access the form data from the service to perform the required actions? I thought about accessing the directive variable from the service, but I'm not sure how to do it and if this is the right way in the first place.
The service should hold a model which is basically the javascript object of your form.
The directive should inject the service and add that object on his scope(a reference).
The directive's template should speak with the directive's scope and display the form.
Changing a value on the view will reflect the service since we they have the same reference and the view will update the directives scope since there is two way binding.
admittedly I'm still working things out, but I think if you add in a controller between your directive and service things will be bit clearer. This is the most compact example of the basic structure I've been playing with.. (forgive the coffeescript if that's not your thing).
angular.module 'app'
.controller 'KeyFormCtrl', (SessionService, $scope, $location, $log) ->
#error = null
#message = null
#keySent = false
#creds =
username: null
#sendKey = ->
SessionService.sendKey({ username: #creds.username })
.then(
(resp) =>
#keySent = true
(err) =>
#error = err.error
)
.directive 'eaKeyForm', ->
{
restrict: 'AE'
scope: {}
templateUrl: 'session/key-form.html'
replace: true
controller: 'KeyFormCtrl'
controllerAs: 'kfc'
bindToController: true
}
session/key-form.html:
<form>
<div ng-show="kfc.error">{{kfc.error}}</div>
<div ng-show="kfc.message">{{kfc.message}}</div>
<div ng-show="kfc.keySent">
An account key has been sent to {{kfc.creds.username}}.<br />
</div>
<div ng-hide="kfc.keySent">
<input type="email" ng-model="kfc.creds.username">
<button ng-click="kfc.sendKey()">Send Key</button>
</div>
</form>
angular.module('myApp', [])
.directive('myAweseomDirective', ['MyAwesomeService', function(MyAwesomeService) {
return {
link: function(scope) {
scope.saveFormDetails = function() {
MyAweseomeService.saveInformation(scope.data);
//where data is the ng-model for the form
}
}
};
}])
.service('MyAweseomService', function() {
MyAwesomeService.saveInformation = function(data) {
//do whatever you want to with the data
}
});

How to access parameter from nested state in parent state?

I have an example here. How i can access 'itemId' parameter in the parent state controller? Parent view must not be reloaded.
angular.module('app',['ui.router'])
.config(function($stateProvider){
$stateProvider.state('step', {
url: '/orders/:step',
templateUrl: 'parent.html',
controller: function ($scope, $stateParams) {
$scope.itemId = $stateParams.itemId;
$scope.step = $stateParams.step;
}
})
.state('step.item', {
url: '/:itemId',
templateUrl: 'child.html',
controller: function ($scope, $stateParams) {
$scope.itemId = $stateParams.itemId;
$scope.step = $stateParams.step;
}
});
}).controller('SimpleController', function($scope, $state){
$scope.items = [1,2,3,4,5,6];
$scope.steps = ["first", "second"];
})
I see only two ways:
add an service and inject it to both controllers
access a parent scope from a child controller and pass a parameter
But both cause me to add watchers at a parent scope.
Maybe i can accomplish it easier?
First, you need to recognize that a parent state's controller runs only once when you enter the subtree of its child states, so switching between its children would not re-run the controller.
This is to say that if you want the itemId parameter to always be up to date, you'd need a $watch (which you tried to avoid).
For the first time, you could collect the itemId like so in the parent's state:
$scope.itemId = $state.params.itemId;
If you need it to be kept up-to-date, then you'd need to watch for changes, for example:
$scope.$watch(function(){
return $state.params;
}, function(p){
$scope.itemId = p.itemId;
});
plunker
Similarly, if you only need to place it in the view, then set $state on the scope:
$scope.$state = $state;
and in the view:
<div>parent /orders/{{step}}/{{$state.params.itemId}}</div>
EDIT:
I guess another way would be to call a scope-exposed function on the parent to update the value. I'm not a fan of such an approach, since it relies on scope inheritance (which is not always the same as state inheritance) and scope inheritance in a large application is difficult to track. But it removes the need for a $watch:
In the parent controller:
$scope.updateItemId = function(itemId){
$scope.itemId = itemId;
};
and in the child controller:
if ($scope.updateItemId) $scope.updateItemId($stateParams.itemId)
I can see, that you've already found your answer, but I would like to show you different approach. And I would even name it as "the UI-Router built in approach".
It has been shown in the UI-Router example application, where if we go to child state with some ID = 42 like here we can also change the other, than the main view (the hint view in this case).
There is a working example with your scenario, showing that all in action.
What we do use, is the Multiple Named Views
The parent state, now defines root view, which is injected into unnamed ui-view="" inside of the root/index.html.
A new parent.html
<div ui-view="title"></div>
<div ui-view=""></div>
As we can see, it also contains another view target (than unnamed) - e.g. ui-view="title" which we fill with another template immediately... in parent state:
$stateProvider.state('step', {
url: '/orders/:step',
views : {
'': {
templateUrl: 'parent.html',
},
'title#step': {
templateUrl:'parent_title_view.html',
}
}
})
And child? It can continue to handle main area... but also can change the title area. Both views are independent, and belong to child.
.state('step.item', {
url: '/:itemId',
views : {
'': {
templateUrl: 'child.html',
},
'title': {
templateUrl:'child_title_view.html',
}
}
So, there are no watchers, nothing... which is not shipped with the UI-Router by default. We just adjust many places (views) with the child implementation. We can still provide their content with some defaults in parent..
Check that example here. Hope it will help to see it a bit differently...
I'm not sure if this will really solve your problem but you can do
$stateProvider.state('step', {
url: '/orders/:step/:itemId',
templateUrl: 'parent.html',
http://plnkr.co/edit/sYCino1V0NOw3qlEALNj?p=preview
This means the parent state will collect the itemID on the way through.
include $state in resolve function for parent state
.state('parent', {
url: '/parent',
controller: 'parentController',
resolve: {
params: ['$state', function ($state) {
return $state.params;
}]
}
})
then in parent controller inject the resloved vars
app.controller('parentController', ['$scope', 'params',
function ($scope, params) {
$scope.params = params;
}]);
in this way params available in both child and parent.

Call angular controller's method from outside

Here's the code: http://jsbin.com/rucatemujape/1/edit?html,js,console,output
My question is how do I manually call method changeUser from JavaScript so the output HTML changes?
I can do this by executing (relies on jQuery)
angular.element('body').scope().changeUser({fullName: 'John Doe'});
angular.element('body').scope().$apply()
But I want to know is there any better way?
For example, in knockout.js I can execute viewModel.someFunction() any time and knockout correctly handles this.
Why do I want to do this: because I want be able to change model from browser's console when debugging a code.
Edit: Another reason why I need this it's getting information from Restful Services and updating a model. Yes I can trigger that event by clicking a button with "ng-click" attribute but how to deal with events which are not initiated by user? For example, repeating ations from setInterval or something
var myApp = angular.module('myApp', []);
myApp.controller('MyController', function($scope, $timeout) {
$scope.user = {};
$scope.count = 0;
$scope.changeUser = function(user) {
$scope.user = "MyName";
$scope.count++;
// call function after 1 sec.
$timeout($scope.changeUser, 1000);
};
// initiate function
$scope.changeUser();
});
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.min.js"></script>
</head>
<body ng-controller="MyController"
<span>Hello, {{user}}</span>
<span>count, {{count}}</span>
</body>
</html>
Use ng-click="changeUser()" to call the fucntion
http://jsbin.com/rucatemujape/2/edit
Controllers do not really expose "APIs" outside their own 'scope' (and below). So either you do the Ajax call within the controller or you expose the "user data" to display.
For example: Kostia Mololkin's answer's (in main comments) uses a global variable to share the data.
Angular Way
Here I present a more "angular way": the user is being handled by a "Service". Not clear which direction you wanted exactly. Anyway, this cannot hurt.
Angular works best when you use "data" to "communicate state" instead of "events". So the idea again is to arrange things so your data instance (i.e. "user") is the same between "{{user.fullname}}" and where the Ajax call is taking place. There are many ways to skin a cat and it highly depends on the needs of your application. For example, if you build a singleton service for the ajax call, you can also have the data own by it AND exposed to the controller in some form or another (again, many ways of doing this).
NOTE: If you use angular's system to perform ajax calls (i.e. $http or $resource for instance) then you should never need to manually call "$apply()". Btw, you should "wrap" calls with $apply() instead of calling it "afterwards" reason being to properly handle "throws".
Plunker example with Ajax/Rest call:
http://plnkr.co/edit/SCF2XZCK5KQWkb4hZfOO?p=preview
var myApp = angular.module('myApp', []);
myApp.factory('UserService', ['$http',function($http){
// Extra parent object to keep as a shared object to 'simplify'
// updating a child object
var userData = {};
userData.user = { fullName:'none' };
function loadUserData(userid) {
$http.get('restapi_getuserdata_'+userid+'.json').
success(function(data, status, headers, config) {
// update model
userData.user = data;
}).
error(function(data, status, headers, config) {
console.log("error: ", status);
});
}
return {
userData: userData,
loadUserData: loadUserData
};
}]);
myApp.controller('MyController', ['$scope', 'UserService', '$timeout',
function($scope, UserService, $timeout) {
// shared object from the Service stored in the scope
// there are many other ways, like using an accessor method that is
// "called" within the HTML
$scope.userData = UserService.userData;
}]);
myApp.controller('SomeOtherController', ['UserService', '$timeout',
function(UserService, $timeout) {
// $timeout is only to simulate a transition within an app
// without relying on a "button".
$timeout(function(){
UserService.loadUserData(55);
}, 1500);
}]);
HTML:
<html ng-app="myApp">
...
<body ng-controller="MyController">
<span>Hello, {{userData.user.fullName}}</span>
<!-- simulating another active piece of code within the App -->
<div ng-controller="SomeOtherController"></div>
...
A variant using a getter method instead of data:
http://plnkr.co/edit/0Y8gJolCAFYNBTGkbE5e?p=preview
var myApp = angular.module('myApp', []);
myApp.factory('UserService', ['$http',function($http){
var user;
function loadUserData(userid) {
$http.get('restapi_getuserdata_'+userid+'.json').
success(function(data, status, headers, config) {
console.log("loaded: ", data);
// update model
user = data;
}).
error(function(data, status, headers, config) {
console.log("error: ", status);
user = undefined;
});
}
return {
getCurrentUser: function() {
return user || { fullName:"<none>" };
},
userLoggedIn: function() { return !!user; },
loadUserData: loadUserData
};
}]);
myApp.controller('MyController', ['$scope', 'UserService', '$timeout',
function($scope, UserService, $timeout) {
// getter method shared
$scope.getCurrentUser = UserService.getCurrentUser;
}]);
myApp.controller('SomeOtherController', ['UserService', '$timeout',
function(UserService, $timeout) {
// $timeout is only to simulate a transition within an app
// without relying on a "button".
$timeout(function(){
UserService.loadUserData(55);
}, 1500);
}]);
HTML:
<html ng-app="myApp">
...
<body ng-controller="MyController">
<span>Hello, {{ getCurrentUser().fullName }}</span>
<!-- simulating another active piece of code within the App -->
<div ng-controller="SomeOtherController"></div>
...

Syncing data between controllers through a service

From this stackoverflow question, my understanding is that I should be using services to pass data between controllers.
However, as seen in my example JSFiddle, I am having trouble listening to changes to my service when it is modified across controllers.
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, App) {
$scope.status = App.data.status;
$scope.$watch('App.data.status', function() {
$scope.status = App.data.status;
});
})
.controller('Ctrl2', function ($scope, App) {
$scope.status = App.data.status;
$scope.$watch('status', function() {
App.data.status = $scope.status;
});
})
.service('App', function () {
this.data = {};
this.data.status = 'Good';
});
In my example, I am trying to subscribe to App.data.status in Ctrl1, and I am trying to publish data from Ctrl1 to App. However, if you try to change the input box in the div associated with Ctrl2, the text does not change across the controller boundary across to Ctrl1.
http://jsfiddle.net/VP4d5/2/
Here's an updated fiddle. Basically if you're going to share the same data object between two controllers from a service you just need to use an object of some sort aside from a string or javascript primitive. In this case I'm just using a regular Object {} to share the data between the two controllers.
The JS
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, App) {
$scope.localData1 = App.data;
})
.controller('Ctrl2', function ($scope, App) {
$scope.localData2 = App.data;
})
.service('App', function () {
this.data = {status:'Good'};
});
The HTML
<div ng-controller="Ctrl1">
<div> Ctrl1 Status is: {{status}}
</div>
<div>
<input type="text" ng-model="localData1.status" />
</div>
<div ng-controller="Ctrl2">Ctrl2 Status is: {{status}}
<div>
<input type="text" ng-model="localData2.status" />
</div>
</div>
Nothing wrong with using a service here but if the only purpose is to have a shared object across the app then I think using .value makes a bit more sense. If this service will have functions for interacting with endpoints and the data be sure to use angular.copy to update the object properties instead of using = which will replace the service's local reference but won't be reflected in the controllers.
http://jsfiddle.net/VP4d5/3/
The modified JS using .value
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, sharedObject) {
$scope.localData1 = sharedObject;
})
.controller('Ctrl2', function ($scope, sharedObject) {
$scope.localData2 = sharedObject;
})
.value("sharedObject", {status:'Awesome'});
I agree with #shaunhusain, but I think that you would be better off using a factory instead of a service:
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, App) {
$scope.localData1 = App.data;
})
.controller('Ctrl2', function ($scope, App) {
$scope.localData2 = App.data;
})
.factory('App', function () {
var sharedObj = {
data : {
status: 'Good'
}
};
return sharedObj;
});
Here are some information that might help you understand the differences between a factory and a service: When creating service method what's the difference between module.service and module.factory

Categories

Resources