I have a function that two controllers will be using, and instead of both of them having the same source code for the same function, I want it in one place and just inject the controller parameters (or perhaps the controller itself this). These three may exist in three separate files/modules.
.controller('FirstCtrl', function() {
this.search = function(this) {
find(this);
};
});
.controller('SecondCtrl', function() {
this.seek = function(this) {
find(this);
};
});
var find = function(controller) {
.
.
.
};
Is this the best way? How about if I have services in my controllers like $http or $scope, and the function find would depend on these services? How do I inject these angular specific services to a plain JavaScript function not defined in an AngularJS module?
There are a few ways to do it; one may be:
.factory("findMixin", function() {
return {
find: function() {
// your implementation; `this` will be the controller
}
};
})
.controller("SomeCtrl", ["$scope", "findMixin", function($scope, findMixin) {
angular.extend(this, findMixin);
// here `this`, i.e. the controller, has received the methods from the mixin
...
})
The same principle (angular.extend) can be applied to the $scope, if you want find to be mixed into the scope.
You can add a service:
.factory('find', [ function() {
return function(controller, scope) {
// ...
};
}]);
And inject it into the controllers:
.controller('FirstCtrl', ['find', function(find) {
this.search = function(this) {
find(this);
};
}]);
.controller('SecondCtrl', ['find', function(find) {
this.seek = function(this) {
find(this);
};
}]);
Related
I have a variable 'file' that is being passed to a directive which i am using in the same controller. Now i want to use that same 'file' in the factory that i am creating but i'm not sure if there is an easy way to share that same variable between controller and factory.
for example...
fileCategory.directive.js:
.directive('fileCategory', function () {
return {
templateUrl: '...'
restrict: 'EA',
replace: true,
scope: {
file: '='
},
controller: 'fileCategoryController'
};
});
fileCategory.controller.js:
.controller('fileCategoryController', function($scope) {
if(!$scope.file) {
return;
} else {
console.log($scope.file);
}
fileCategory.factory.js
.factory('fileCategoryList', function () {
categories.get = function() {
if($scope.file){
return this.categories;
} else{
return;
}
};
I want to be able to use $scope.file in my factory like so...
Using $rootScope is possible here, but please don't use it in this case. Better practice is use service for storing data, and manipulate between different components. When your application will grow, it can be problem store more data in global $rootScope.
.service('CategoryService', function () {
this.file = ...
}
then implement service to controller, factory or anywhere you need
.controller('fileCategoryController', function($scope, CategoryService ) {
$scope.file = CategoryService.file
if(!CategoryService.file) {
return;
} else {
console.log($scope.file);
}
define([], function() {
function myCtrl($scope,$http)
{
$scope.test = "Course Man";
}
myCtrl.$inject=['$scope','$http'];
return myCtrl;
});
We have separate file for each controller and lazy loaded when required.. They have corresponding entry in application.js.
Now the problem is :
I need 2-3 child controllers all linked to a parent controller.. and all are there in a single file.. so that they can be loaded..
Tried :
define([], function() {
function myCtrl($scope,$http)
{
$scope.test = "Course Man";
}
function myCtrl1($scope,$http){};
myCtrl.$inject=['$scope','$http'];
return myCtrl;
});
but, dosen't seems to be working.
UPDATE ----
Parent --
define([], function() {
function myCtrl($scope,$http)
{
$scope.test = "Course Man";
}
myCtrl.$inject=['$scope','$http'];
return myCtrl;
});
With another controller :
define([], function() {
function myCtrl($scope,$http)
{
$scope.test = "Course Man";
}
return myCtrl;
});
function myCtrl1($scope,$http){
};
This is working .. not sure they have parent child relationship or not... confused !
You can go the other way.
it is possible to extend a controller or make a single controller a mixin of multiple controllers.
module.controller('CtrlChild', ['$scope', '$controller', function ($scope, $controller) {
// Initialize the super class and extend it.
angular.extend(this, $controller('CtrlParent', {$scope: $scope}));
… Additional extensions to create a mixin.
}]);
I am building an angular single page app, which have a structure like this.
"app.parent - parent state"
"app.parent.childState - child state"
"app.parent.childSatate" has 4 multiple named view inside it.
I have to show something on parent once all 4 views fetched their respective data.
Any suggestions how to do it?
Note: If Solution is pure independent it helps me alot, if I delete/add any controller then i need not to make changes to parent all the time.
Suppose you have 4 services that you know will be the data source for 4 different child views. You can setup your services so that they return references, but still provide access to the underlying promises. The idea is that you want your views to use the reference when rendering the view; the parent will use $q.all to wait until the individual promises are resolved before it shows something.
Factories
app.factory('service1', function($http) {
var data1 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data1);
}),
getData: function() {
return data1;
}
}
});
app.factory('service2', function($http) {
var data2 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data2);
}),
getData: function() {
return data2;
}
}
});
app.factory('service3', function($http) {
var data3 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data3);
}),
getData: function() {
return data3;
}
}
});
app.factory('service4', function($http) {
var data4 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data1);
}),
getData: function() {
return data4;
}
}
});
Child Controllers
app.controller('ctrl1', function($scope, service1) {
$scope.data1 = service1.getData();
});
app.controller('ctrl2', function($scope, service2) {
$scope.data2 = service2.getData();
});
app.controller('ctrl3', function($scope, service3) {
$scope.data3 = service3.getData();
});
app.controller('ctrl4', function($scope, service4) {
$scope.data4 = service4.getData();
});
Parent Controller
app.controller('parent', function($scope, $q, service1, service2, service3, service4) {
$q.all(
[service1.promise,
service2.promise,
service3.promise,
service4.promise])
.then(function() {
$scope.done = true;
});
});
Parent View
<div ng-show="done"> All child data loaded </div>
A More Modular Approach
As mentioned in the original post - it would be nice if the parent controller did not have to depend on the injection of the individual data sources. That way, when the child changes data sources (adds sources, or removes sources), the parent controller is not impacted.
This can be done by relying on directive-to-directive communication. The idea is that the child directives can register their data sources with the parent directive. Once all the data sources have been registered, the parent directive can use $q.all as in the first example.
Parent Directive
app.directive('parent', function($q) {
return {
restrict: 'A',
require: 'parent',
controller: function($scope) {
$scope.done = false;
var promises = [];
this.register = function(promise) {
promises.push(promise);
}
this.getPromises = function() {
return promises;
}
},
link: function(scope, element, attr, parent) {
$q.all(parent.getPromises())
.then(function() {
scope.done = true;
});
}
}
});
Child Directive
app.directive('child', function(service1) {
return {
restrict: 'A',
require: '^parent',
controller: function($scope) {
$scope.data1 = service1.getData();
},
link: function(scope, element, attr, parent) {
parent.register(service1.promise);
}
}
});
HTML
<div parent>
<div child>
<div> {{data1}} </div>
</div>
</div>
Yet Another Approach
You may have noticed that although the second approach does not impact the parent controller, the child controller has a dependency on the parent directive. How can we eliminate this view dependency from the child controller?
Use services. Create a parent service that allows child controllers to register their promises; and also expose a method from the parent service that returns a promise which is resolved when all the child promises have been resolved.
This may be the preferred approach when you have a single point of loading for the entire page.
Parent Service
app.factory('parentService', function($q) {
var promises = [];
return {
promise: function() {
return $q.all(promises);
},
register: function(promise) {
promises.push(promise);
}
}
});
Child Controller
app.controller('childCtrl1', function(parentService, service1) {
parentService.register(service1.promise);
});
app.controller('childCtrl2', function(parentService, service2) {
parentService.register(service2.promise);
});
Parent Directive
app.directive('parent', function(parentService) {
return {
restrict: 'A',
controller: function($scope) {
$scope.done = false;
},
link: function(scope) {
parentService.promise().then(function() {
scope.done = true;
});
}
}
});
HTML
<div parent>
<div ng-show="done">All child data loaded</div>
<div ng-controller="childCtrl1">
{{ data1 }}
</div>
<div ng-controller="childCtrl2">
{{ data2 }}
</div>
</div>
Use $rootScope (recommended factory/service) in all four controllers (for all 4 views), and have certain count/flag which keeps track of data fetching in all controllers and on the main page/controller keep checking for that $rootScope/factory value.
$rootScope/ facetory/services are accessible across controllers/views, so that you can modify in several controllers and access the latest value on some other controller/factory/service.
Related documentations:
https://docs.angularjs.org/api/ng/service/$rootScope
https://docs.angularjs.org/guide/services
Create a service or factory and inject it into the controllers you need it in, that way you will not have to repeat code.
You can also use this method to share data across your app by making variables avalible via dependency injection.
With ui-router, I add all resolve logic in state function like this;
//my-ctrl.js
var MyCtrl = function($scope, customers) {
$scope.customers = customers;
}
//routing.js
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controller: 'MyCtrl',
resolve: { // <-- I feel this must define as like controller
customers: function(Customer, $stateParams) {
return Customer.get($stateParams.id);
}
}
});
However IMO, resolve object must belong to a controller, and it's easy to read and maintain if it is defined within a controller file.
//my-ctrl.js
var MyCtrl = function($scope, customers) {
$scope.customers = customers;
}
MyCtrl.resolve = {
customers: function(Customer, $stateParams) {
return Customer.get($stateParams.id);
};
};
//routing.js
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controller: 'MyCtrl',
resolve: 'MyCtrl.resolve' //<--- Error: 'invocables' must be an object.
});
However, When I define it as MyCtrl.resolve, because of IIFE, I get the following error.
Failed to instantiate module due to: ReferenceError: MyCtrl is not defined
When I define that one as string 'MyCtrl.resolve', I get this
Error: 'invocables' must be an object.
I see that controller is defined as string, so I think it's also possible to provide the value as string by using a decorator or something.
Has anyone done this approach? So that I can keep my routings.js clean and putting relevant info. in a relevant file?
It sounds like a neat way to build the resolve, but I just don't think you can do it.
Aside from the fact that "resolve" requires an object, it is defined in a phase where all you have available are providers. At this time, the controller doesn't even exist yet.
Even worse, though, the "resolve" is meant to define inputs to the controller, itself. To define the resolve in the controller, then expect it to be evaluated before the controller is created is a circular dependency.
In the past, I have defined resolve functions outside of the $stateProvider definition, at least allowing them to be reused. I never tried to get any fancier than that.
var customerResolve = ['Customer', '$stateParams',
function(Customer, $stateParams) {
return Customer.get($stateParams.id);
}
];
// ....
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controller: 'MyCtrl',
resolve: {
customers: customerResolve
}
});
This question is about features of ui-router package. By default ui-router doesn't support strings for resolve parameter. But if you look at the source code of ui-router you will see, that it's possible to implement this functionality without making direct changes to it's code.
Now, I will show the logic behind suggested method and it's implementation
Analyzing the code
First let's take a look at $state.transitionTo function angular-ui-router/src/urlRouter.js. Inside that function we will see this code
for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
locals = toLocals[l] = inherit(locals);
resolved = resolveState(state, toParams, state === to, resolved, locals, options);
}
Obviously this is where "resolve" parameters are resolved for every parent state. Next, let's take a look at resolveState function at the same file. We will find this line there:
dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
var promises = [dst.resolve.then(function (globals) {
dst.globals = globals;
})];
This is specifically where promises for resolve parameters are retrieved. What's good for use, the function that does this is taken out to a separate service. This means we can hook and alter it's behavior with decorator.
For reference the implementation of $resolve is in angular-ui-router/src/resolve.js file
Implementing the hook
The signature for resolve function of $resolve is
this.resolve = function (invocables, locals, parent, self) {
Where "invocables" is the object from our declaration of state. So we need to check if "invocables" is string. And if it is we will get a controller function by string and invoke function after "." character
//1.1 Main hook for $resolve
$provide.decorator('$resolve', ['$delegate', '$window', function ($delegate, $window){
var service = $delegate;
var oldResolve = service.resolve;
service.resolve = function(invocables, locals, parent, self){
if (typeof(invocables) == 'string') {
var resolveStrs = invocables.split('.');
var controllerName = resolveStrs[0];
var methodName = resolveStrs[1];
//By default the $controller service saves controller functions on window objec
var controllerFunc = $window[controllerName];
var controllerResolveObj = controllerFunc[methodName]();
return oldResolve.apply(this, [controllerResolveObj, locals, parent, self]);
} else {
return oldResolve.apply(this, [invocables, locals, parent, self]);
}
};
return $delegate;
}]);
EDIT:
You can also override $controllerProvider with provider like this:
app.provider("$controller", function () {
}
This way it becomes possible to add a new function getConstructor, that will return controller constructor by name. And so you will avoid using $window object in the hook:
$provide.decorator('$resolve', ['$delegate', function ($delegate){
var service = $delegate;
var oldResolve = service.resolve;
service.resolve = function(invocables, locals, parent, self){
if (typeof(invocables) == 'string') {
var resolveStrs = invocables.split('.');
var controllerName = resolveStrs[0];
var methodName = resolveStrs[1];
var controllerFunc = $controllerProvider.getConstructor(controllerName);
var controllerResolveObj = controllerFunc[methodName]();
return oldResolve.apply(this, [controllerResolveObj, locals, parent, self]);
} else {
return oldResolve.apply(this, [invocables, locals, parent, self]);
}
};
Full code demonstrating this method http://plnkr.co/edit/f3dCSLn14pkul7BzrMvH?p=preview
You need to make sure the controller is within the same closure as the state config. This doesn't mean they need to be defined in the same file.
So instead of a string, use a the static property of the controller:
resolve: MyCtrl.resolve,
Update
Then for your Controller file:
var MyCtrl;
(function(MyCtrl, yourModule) {
MyCtrl = function() { // your contructor function}
MyCtrl.resolve = { // your resolve object }
yourModule.controller('MyCtrl', MyCtrl);
})(MyCtrl, yourModule)
And then when you define your states in another file, that is included or concatenated or required after the controller file:
(function(MyCtrl, yourModule) {
configStates.$inject = ['$stateProvider'];
function configStates($stateProvider) {
// state config has access to MyCtrl.resolve
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controller: 'MyCtrl',
resolve: MyCtrl.resolve
});
}
yourModule.config(configStates);
})(MyCtrl, yourModule);
For production code you will still want to wrap all these IIFEs within another IIFEs. Gulp or Grunt can do this for you.
If the intention is to have the resolver in the same file as the controller, the simplest way to do so is to declare the resolver at the controller file as a function:
//my-ctrl.js
var MyCtrl = function($scope, customers) {
$scope.customers = customers;
}
var resolverMyCtrl_customers = (['Customer','$stateParams', function(Customer, $stateParams) {
return Customer.get($stateParams.id);
}]);
//routing.js
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controller: 'MyCtrl',
resolve: resolverMyCtrl_customers
});
This should work.
//my-ctrl.js
var MyCtrl = function($scope, customer) {
$scope.customer = customer;
};
//routing.js
$stateProvider
.state('customers.show', {
url: '/customers/:id',
template: template,
resolve: {
customer: function(CustomerService, $stateParams){
return CustomerService.get($stateParams.id)
}
},
controller: 'MyCtrl'
});
//service.js
function CustomerService() {
var _customers = {};
this.get = function (id) {
return _customers[id];
};
}
I have this:
app.controller('foo1', function ($scope) {
$scope.bar = 'foo';
});
app.controller('foo2', function ($scope) {
// want to access the $scope of foo1 here, to access bar
});
How would I accomplish this?
You could use an Angular Service to share variable acrosss multiple controllers.
angular.module('myApp', [])
.service('User', function () {
return {};
})
To share the data among independent controllers, Services can be used. Create a service with the data model that needs to be shared. Inject the service in the respective controllers.
function ControllerA($scope, User) {
$scope.user = User;
$scope.user.firstname = "Vinoth";
}
function ControllerB($scope, User) {
$scope.user = User;
$scope.user.lastname = "Babu";
}
You just can use $emit/$broadcast for translate changes of data from one controller scope to another. Or just store these variables on $rootScope.
app.controller('foo2', function ($scope) {
$scope.$$prevSibling.bar="bar"
});
app.controller("firstCtrl", function ($scope) {
$scope.func = function () {
// pass scope variable(s) here
$scope.$broadcast('parentmethod', { key: value });
}
})
app.controller("secondCtrl", function ($scope) {
$scope.$on('parentmethod', function (event, args) {
// access scope variable using args
$scope.targetVar = args.key;
})
})