This is something I discovered when trying to make my controllers more reusable.
app.factory('BaseController', function(myService) {
return function(variable) {
this.variable = variable;
this.method = function() {
myService.doSomething(variable);
};
};
})
.controller("ChildController1", function($scope, BaseController) {
BaseController.call($scope, "variable1");
})
.controller("ChildController2", function($scope, BaseController) {
BaseController.call($scope, "variable2");
});
And now I can do something like this in my HTML (e.g inside ng-controller="ChildController1"): ng-click="method()"
The code simply works. But I don't know how it really works (what kind of pattern is it?) and would it be a good practice to do so?
Your solution works more or less like this:
app.factory('mixinBaseController', function(myService) {
return function (scope, variable) {
angular.extend(scope, {
variable: variable;
method: function() {
myService.doSomething(variable);
}
});
};
})
.controller("ChildController1", function($scope, mixinBaseController) {
mixinBaseController($scope, "variable1");
})
.controller("ChildController2", function($scope, mixinBaseController) {
mixinBaseController($scope, "variable2");
});
Can you see? By your .call($scope, ...) is just setting the context (this) to the real $scope and this way the BaseController just extends your scope with properties and functions.
This is only to demonstrate how your code works.
To achieve a more JavaScript like inheritance, please see:
Can an AngularJS controller inherit from another controller in the same module?
AngularJS controller inheritance
AngularJS Inheritance Patterns
Two Approaches to AngularJS Controller Inheritance
You should as well have a look at the "controllerAs" syntax introduced in AngularJS 1.2. I highly recommend to use controllerAs. This also should help to implement inheritance for your controllers.
Related
I'm building a small two-language app with the use of angular-translate. I want to have a language switcher in every view (controller). I'm trying to figure out how to put the code responsible for language switching into every controller. The code looks like this:
var langSwitch = $Scope.setLang = function (langKey) {
$translate.use(langKey);
};
So far I've figured that I can create a factory that looks like this:
app.factory('langSwitch', function ($rootScope, $translate) {
var langSwitch = $rootScope.setLang = function (langKey) {
$translate.use(langKey);
};
return langSwitch;
});
and inject it into controllers in this maner:
app.controller('HomeCtrl', function (langSwitch) {
// normal controller code here
});
This works but 1) I'm using $rootScope and I have a feeling this is bad practice & 2) jsHint screams that "langSwitch" is not defined. Maybe there is a simpler way to make the function global without putting it into every controller?
I'm still pretty new to Angular so don't scream at me :) Thanks.
edit
My view:
<button ng-click="setLang('en_GB')">English</button>
<button ng-click="setLang('pl_PL')">Polish</button>
Although you got the idea, you overcomplicated things a bit. You could declare the service as follows:
app.service('langSwitch', function ($translate) {
this.setLang = function (langKey) {
$translate.use(langKey);
};
});
And then inject langSwitch in the controller responsible for lang switching, as you already did. No need to inject $rootScope in the service.
You don't need $rootScope indeed unless you need to process some global events in your application. All services and factories in angular are singletons by default. That means once it created, it will be passed as the same instance in every place it is declared as a dependency. So if you want to share data and functionality between different controllers - the services will suit fine. You can change your factory code to:
app.factory('langSwitch', function($translate) {
return {
setLang: function(langKey) {
$trasnlate.use(langKey);
};
};
});
Is there a correct way to pass a non-angular callback (preferably an anonymous function) name to a directive and then have it activated from the directive. My motivation is to interface from the directive to legacy non-angular code.
Here is what I want:
<my-Directive callback="function (param) {some-legacy-function(param)}">
and then have it activated:
app.directive('myDirective', function($scope) {
return {
scope: {
callback: '&'
}
controller: function($scope) {
$scope.someMethod = function() {
$scope.callback($scope.param);
}
}
});
Note that callback function is not defined on any scope and should preferably anonymous (but I can manage with a non-anonymous function as well).
I found one of doing this by simply passing a string and then doing an eval to call the method but after reading around I understand this approach is problematic (security wise).
Any suggestions would be appreciated.
you can wrap your legacy code inside a injectable service, so you can inject it anywhere (and replace/mock it for tests)
// here set on global for example
var legacyFunction=function(){};
//angular code (has to be loaded after your "old" script
myApp.factory('legacyFunction',function(){
return legacyFunction;
})
//now it is available anywhere in you application (directives, controllers, etc) as an injectable -here in a controller so you can bind it to a $scope for example
myApp.controller('myController',['$scope', 'legacyFunction', function($scope, func){
$scope.myFunc = func
}])
I'm trying to figure out the "preferred" or "angular-way" of sharing properties or state between controllers/directives. There are several methods to implement this, but I want to keep with best-practice. Below are some banal examples of how this can be implemented:
1. Using $scope.$watch
// The parent controller/scope
angular.module('myModule').controller('parentController', ['$scope', function($scope) {
$scope.state = {
myProperty: 'someState'; // Default value to be changed by some DOM element
};
}]);
// The child controller/scope.
angular.module('myModule').controller('childController', ['$scope', function($scope) {
$scope.$watch('state.myProperty', function (newVal) {
// Do some action here on state change
});
}]);
Edit: Based on answers below, this is bad practice and should be avoided. It is untestable and places an unwanted DOM dependancy.
2. Using $broadcast
// The parent controller
angular.module('myModule').controller('parentController', ['$scope', function($scope) {
var myProperty = 'someState';
$scope.setState = function (state) {
myProperty = state; // Set by some other controller action or DOM interaction.
$scope.$broadcast('stateChanged', state); // Communicate changes to child controller
}
}]);
// The child controller.
angular.module('myModule').controller('childController', ['$scope', function($scope) {
$scope.$on('stateChanged', function (evt, state) {
// Do some action here
}
}]);
Edit: Equally bad practice as you need to know the placement of the controllers in the DOM in order to determine weather to use $broadcast (down the DOM) or $emit (up the DOM).
3. Using service
angular.module('myModule').factory('stateContainer', [function () {
var state = {
myProperty: 'defaultState'
},
listeners = [];
return {
setState: function (newState) {
state.myProperty = newState;
angular.forEach(listeners, function (listener) {
listener(newState);
});
},
addListener: function (listener) {
listeners.push(listener);
}
}
}]);
// The parent controller
angular.module('myModule').controller('parentController', ['$scope', 'stateContainer', function($scope, stateContainer) {
$scope.setState = function (state) {
stateContainer.setState(state);
};
}]);
// The child controller.
angular.module('myModule').controller('childController', ['$scope', 'stateContainer', function($scope, stateContainer) {
stateContainer.addListener(function (newState) {
// Do some action here
});
}]);
There are probably some approaches I've missed here, but you get the idea. I'm trying to find the best approach. Although verbose, I personally lean towards #3 in the list here. But I come from a Java and jQuery background where listeners are widely used.
Edit: Answers below are insightful. One talks of sharing state between parent/child directives using the require directive configuration. The other talks of sharing service or service properties directly to the scope. I believe that depending on the need, they are both right in what is or is not best practice in Angular.
Any of these will work if done correctly, but a variant on service is the preferred way AFAIK.
The question is, do you even need a listener in the service case? Angular itself will update any views (which is the purpose of the controller), so why do you need a listener or watch? It is sufficient to change the value itself for the view to be changed.
app.factory('stateService',function() {
return {
myState: "foo"
}
})
.controller('one',function($scope,stateService) {
$scope.changeState = function() {
stateService.myState = $scope.state;
};
})
.controller('two',function($scope,stateService) {
$scope.svc = stateService;
})
You can then do the following in your view (incomplete):
<div ng-controller="one">
<input name="state" ng-model="state"></input>
<button type="submit" ng-click="changeState()">Submit</button>
</div>
<div ng-controller="two">{{svc.myState}}</div>
Truth is, you don't even need to go that far with having a button and a function. If you just tie the ng-model together it will work:
<div ng-controller="one">
<input name="state" ng-model="svc.myState"></input>
</div>
<div ng-controller="two">{{svc.myState}}</div>
Try the following jsfiddle http://jsfiddle.net/cwt9L6vn/1/
There is no such thing as parent and child controllers in AngularJS. There are only parent and child directives, but not controllers. A directive can have a controller that it exposes as an API to other directives.
Controllers are not related to the DOM hierarchy so they can't have children. They also don't create their own scope. So you never know if you have to $broadcast or $emit to talk to other controllers.
If you start using $broadcast from a controller, then you're going to get stuck not knowing if the other controller is up or down. That's when people start doing stuff like $rootScope.$broadcast(..) which is a very bad practice.
What you are looking for are directives that require other directives.
var app = angular.modeul('myApp',[]);
// use a directive to define a parent controller
app.directive('parentDir',function() {
return {
controller: function($scope) {
this.myFoo = function() {
alert("Hello World");
}
}
});
// use a directive to enforce parent-child relationship
app.directive('childDir',function() {
return {
require: '^parentDir',
link: function($scope, $el, $attr, parentCtrl) {
// call the parent controller
parentCtrl.myFoo();
}
});
Using the require feature of a directive does two important things.
Angular will enforce the relationship if it's not optional.
The parent controller is injected into the child link function.
There is no need to $broadcast or $emit.
Another option that is also effective is to use directives to expose an API.
// this directive uses an API
app.directive('myDir',function() {
return {
scope: {
'foo': '&'
},
link: function($scope, $el, $attr) {
// when needed, call the API
$scope.foo();
}
});
// in the template
<div ng-controller="parentController">
<div my-dir foo="parentController.callMyMethod();"></div>
</div>
I'm looking for a way to access both controllers inside the directive.
Currently it's not working and I don't know why or if it's even possible.
If I use both require and controller option, inside the link function ctrl property refers to whatever I requested via the require option.
I can't think of a way to access the controller inside the link function when the require option is present.
It seems that these two properties are mutually exclusive ?
angular.module('moduleName').directive('directiveName', [function () {
return {
controller: 'MediaController',
require:'ngController',
link: function (scope, element, attributes, ctrl) {
// I need access to both controllers here
}
}
}]);
If you want both controllers, then require both controllers:
angular.module('moduleName').directive('directiveName', [function () {
return {
controller: MediaController,
require:['directiveName', 'ngController'],
In this case ctrl is an array containing the two controllers.
Without really knowing why you need to access both controllers, I can only offer minimal advice here. My suggestion would be to create a service to handle cross controller needs. Services are singletons and they support data binding. Services are my preference for cross controller work every day. For example:
App.controller('Ctrl1', function Ctrl1 ($scope, TestService) {
$scope.someValue = TestService.getValue();
});
App.controller('Ctrl2', function Ctrl2 ($scope, TestService) {
$scope.someValue = TestService.getValue();
});
App.factory('TestService', function() {
var myVal = "I Bound";
return {
getValue: function() {
return myVal;
}
}
});
This method allows you to abstract a controllers need to directly access another controller. Your services can be pulled into these directives or other services too. I hope this helps a bit.
Thanks,
Jordan
I have a service that watches something on the rootscope and does something in response. Nobody else requires that service as a dependency. How do I tell the service to start doing its thing?
This is how I do it for now.
angular.module('app')
.service('myService', function ($rootScope) {
return function () {
$rootScope.$on("...", function () {
//do stuff
});
};
})
.run(function (myService) {
myService();
});
Is there a cleaner way to do this?
I've implemented similar things, but using a directive, and not a service:
app.directive('myDirective', function() {
return {
controller: function($scope) {
$scope.$on('myEvent', function () {
// Do stuff
});
}
}
});
And then used this in the root element of my application, which can, for example, be body:
<body ng-app="app" my-directive>
The benefit of using directives in this way over services is that it makes it a touch less global / final. For example, if you end up wanting some scopes to not be able to $emit events to this handler, then you could move the myDirective to another element. Or you could maybe down the road you'll want a part of your app to respond slightly differently to myEvent, so you can add another instance of myDirective, maybe passing it some options via attributes. On the whole it leaves it a lot more open to adding complex behaviour.