I understand how Angular dependency injection works with directives but wanted clarification on something. I have a dummy test directive as follows:
app.directive("test", [
function() {
return {
restrict: "E",
scope: {},
controller: ["$scope", "$filter",
function($scope, $filter) {
var food = ["Apple pie", "Apple cobler", "Banana Split", "Cherry Pie", "Applesauce"];
$scope.favorites = $filter('filter')(food, "Apple");
}
],
template: "<div>{{favorites}}</div>"
}
}
]);
This works fine and will filter the food array as expected. However I noticed if I inject the $filter service in the directive as follows, it still works:
app.directive("test", ["$filter",
function($filter) {
return {
restrict: "E",
scope: {},
controller: ["$scope",function($scope) {...
My question: Is it better practice to inject services into a directive in the declaration line like so:
app.directive("test", ["$filter", function($filter) {
or in the controller line like this:
controller: ["$scope", "$filter", function($scope, $filter) {?
Is there no difference? Here is a Plunker of the directive code.
In this case, you're receiving the same service, so it likely doesn't matter too much. I personally prefer to keep them as localized as possible; if you don't need $filter in the link function or something like that, I'd just inject it into the controller.
In certain cases, this may also make it easier to mock dependencies during testing.
You can do this also. Much better way by splitting directive and its controller in a single file. Or you can write in separate files. But, better understand
app.directive('throbberDirective',
[
function(){
return {
restrict: "EA",
templateUrl: "common/utils/throbbers/throbber.html",
controller: throbberController
}
}
]);
app.controller('throbberController', throbberController);
throbberController.$inject = ['$scope', '_$ajax'];
function throbberController($scope){
$scope.throbber = _$ajax.getThrobberConfigs();
$scope.throbber.templateName = $scope.throbber.templateName;
}
Related
how i can use something like resolve in config in directives?
I have such code:
angular.module('config', ['ngRoute', 'resources.params'])
.config(['$routeProvider', function($routeProvider) {
'use strict';
$routeProvider
.when('/config',{
templateUrl: 'templates/config/config.tpl.html',
controller: 'ConfigCtrl',
resolve: {
params: ['Params', '$route',
function(Params, $route) {
if ($route.current.params.skey)
return Params.get($route.current.params.skey);
else
return null;
}
]
},
reloadOnSearch: true
});
}
])
.controller('ConfigCtrl', ['$scope','$route','$routeParams', 'params','Params',
function($scope,$route,$routeParams,params,Params){
'use strict';
I can use "params" in my controller because i wrote "params: [..." in my .config
But now i want to use this "params" in my directive:
.directive('mapsysitem', ['$location', '$routeParams', '$freshmark',
function($location, $routeParams, $freshmark) {
'use strict';
return {
restrict: 'E',
require: '^mapsyslist',
scope: {
zoomlist: '#',
item: '=',
skey: '=',
select: '&'
},
replace: true,
templateUrl: 'templates/map/mapsysitem.tpl.html',
controller: ['$element', '$scope', 'System','$filter','Params',
function($element, $scope, System, $filter, Params) {
.....
}]
};
}]);
If i will add "params" to controller options i will have "Unknown provider: paramsProvider <- params". How i can solve this problem? Thaks you.
Due to your use of an isolated scope within your directive, you will need pass the parameter as an attribute in the directive node
Set the scope in the app controller
.controller('ConfigCtrl', ['$scope','$route','$routeParams','params','Params',
function($scope,$route,$routeParams,params,Params){
$scope.myParams=Params
then pass the $scope.myParams to the directive
<mapsysitem param='myParams'></mapsysitem>
Then create the two way binding in your scope
scope: {
param:'='
},
Then you will be able to use this in the directive controller
controller: ['$element', '$scope', 'System','$filter',
function($element, $scope, System, $filter ) {
var myParams= scope.param
I don't think you can get the scope or attributes values of your directive's controller so if you can't you have to use a service (or $rootScope maybe) to share those values (and watch for changes).
Otherwise you may use the link function. You can access to the scope and attributes here. Here is a quote from angular site about controller/link function :
Best Practice: use controller when you want to expose an API to other directives. Otherwise use link.
This mean "use link function unless you're really sure you need controller".
See https://docs.angularjs.org/guide/directive
I have a directive i'm using to do the same search filtering across multiple pages. So the directive will be using a service and get pretty hefty with code. Because of that I want to link to a controller instead of have the controller inside the directive like this:
.directive('searchDirective', function($rootScope) {
return {
restrict: 'E',
templateUrl:'searchtemplate.html',
controller: 'searchCtrl',
controllerAs: 'search'
};
});
I also want access to parent scope data inside the template, so I don't want to use a isolated scope.
Anyway here's what i'm not sure how to do. My directive looks like this:
<search-directive filter="foo"/>
How do I pass in the value in the filter attribute so that I can access it in my controller using $scope.filter or this.filter?
If I were using an isolated scope it'd be simple. If i had the controller in the same page I could use $attrs. But since i'm using a controller from another spot and don't want an isolated scope i'm not sure how to get the attrs values into the controller.
Any suggestions?
What about using the link function and passing the value to the scope?
return {
restrict: 'E',
templateUrl:'searchtemplate.html',
controller: 'searchCtrl',
controllerAs: 'search',
link: function (scope, element, attr) {
scope.filter = attr.filter;
}
};
searchDirective.js
angular
.module('searchDirective', []).controller('SearchCtrl', SearchCtrl)
.directive('SearchDirective', directive);
function directive () {
var directive = {
templateUrl:'searchtemplate.html',
restrict: "E",
replace: true,
bindToController: true,
controller: 'searchCtrl as search',
link: link,
scope: { filter:'=' } // <-- like so here
};
return directive;
function link(scope, element, attrs) {}
}
SearchCtrl.$inject = [
'$scope',
'$filter'];
function SearchCtrl(
$scope,
$filter) {
/** Init SearchCtrl scope */
/** ----------------------------------------------------------------- */
var vs = $scope;
// ....
Also I highly recommend checking out this AngularJS style guide, how you are writing your directive above is how I use to do it too. John Papa shows some way better ways: https://github.com/johnpapa/angular-styleguide
Directives:
https://github.com/johnpapa/angular-styleguide#directives
Controllers:
https://github.com/johnpapa/angular-styleguide#controllers
Flip the values of bindToController and scope around.
{
....
scope: true,
bindToController: { filter:'=' }
...
}
I have just hit the same issue over the weekend, and made a simple complete example here: bindToController Not Working? Here’s the right way to use it! (Angular 1.4+)
It's a well known issue in angular to need to use the special array syntax when bringing in dependencies into controllers to avoid minification problems, so I have been using that notation. But it seems that the injector is still having issues with this code that appear only after sending it through gulp-uglify.
Do other angular elements like directives also need to have this syntax be used? Also, I'm using object notation to define one of the controllers, so might that be the problem?
Some main config stuff.
var app = angular.module('musicApp', ['ngSanitize']);
//Whitelist Soundcloud
app.config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'https://w.soundcloud.com/**'
]);
});
Directives, one with a controller in it.
app.directive('soundcloudHtml', ['$sce', function($sce){
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.musicPiece.soundcloud = $sce.trustAsHtml(scope.musicPiece.soundcloud);
}
}
}]);
app.directive('music', function(){
return {
restrict: 'E',
scope:{
type: '='
},
templateUrl: '/resources/data/music/music.html?po=343we',
link: function(scope, element, attrs) {
},
controller: ['$http', '$scope', function($http, $scope){
this.musicList = [];
$scope.Utils = Utils;
var ctrl = this;
$http.get('/resources/data/music/music.json').success(function(data){
ctrl.musicList = data;
Utils.updateTableOfContents();
});
}],
controllerAs: 'musicCtrl'
};
});
Looks like I missed that config also needs that pattern as well in order to be minified.
The config should be
//Whitelist Soundcloud
app.config(['$sceDelegateProvider', function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'https://w.soundcloud.com/**'
]);
}]);
And not
//Whitelist Soundcloud
app.config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'https://w.soundcloud.com/**'
]);
});
I am looking to specify the controller that a directive should use via an attribute on the element - i.e. dynamically:
HTML
<div data-mydirective data-ctrl="DynController"></div>
Angular
angular.module('app', [])
.controller('MainController', [function() { ... }])
.controller('DynController', [function() { ... }])
.directive('mydirective', [function() {
return {
controller: 'DynController', // <- make this dynamic
...
}
}]);
You can do the following:
.directive('mydirective', [function() {
return {
controller: function($scope, $element, $attrs){
//Make decision based on attributes or $scope members
if($scope.$attrs.caseA == 'true'){
return new ControllerA($scope, $element, $attrs);
} else {
return new ControllerDefault($scope, $element, $attrs);
}
},
...
}
}]);
Taking it a step farther, you can use $controller
.directive('mydirective', ['$controller',function($controller) {
return {
controller: function($scope, $element, $attrs){
return $controller($attrs.dynamicCtrlName, {$scope: $scope,$element:$element,$attrs:$attrs});
},
...
}
}
In both cases, make sure you provide all the specific injectable dependencies (particular $scope, $attrs, transclude function etc...) your controllers expect. More details on controller injectables here $compile under the controller section - ideally, outer controller function should receive them all pass as locals.
Nothing in the docs about this, but you can do the following. From my understanding this stuff is used under the hood to make ng-controller work.
.directive('mydirective', [function() {
return {
controller: '#',
name: 'ctrl', // <- attribute that specifies the controller to use
...
}
}]);
I think a better approach is just use scope since scope is already bound to controller, i.e.
put what you want to call on controller scope and simple handle it in directive link
I have a problem with my directive and controller. The variable item in scope is undefined in directive, even though I passed it in html. This is my code:
app.js:
var app = angular.module("app", ["ngRoute"]);
app.config(["$routeProvider", function($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "views/main.html",
controller: "MainCtrl"
});
}]);
controller.js:
app.controller("MainCtrl", function($scope) {
$scope.item = "x";
});
directive.js:
app.directive("exampleDirective", function() {
return {
restrict: "A",
scope: {
item: "="
},
templateUrl: "views/item.html"
};
});
index.html:
<div ng-view></div>
main.html:
<div example-directive item="item"></div>
item.html:
<div>{{ item }}</div>
UPDATE
I changed my code to:
app.directive("exampleDirective", function() {
return {
restrict: "A",
scope: true,
templateUrl: "views/item.html"
};
});
and now there is "x" in scope.$parent.item. But why it isn't present inside directive?
Although it seems to work just fine with latest Angular stable http://plnkr.co/edit/6oXDIF6P04FXZB335voR?p=preview maybe you are trying to use templateUrl thats pointing to somewhere it doesn't exist.
Another thing, use primitives only when strictly needed. In case you ever need to modify value of item, you won't be able to do so since you are using a primitive. Plus, if you need "more info" to go inside your isolated scopes, and to avoid attribute soup (attr-this="that", attr-that="boop", my-otherstuff="anotheritem.member", etc) you can pass the two-way bind of an object that handle more data.
Or if you need to share state through multiple controllers, directives, etc, use a service instead and use dependency injection, and there's no need to pass in objects/primitives to your isolated scope, and you can assure state, and that's best practice "the Angular way".
This fiddle works: http://jsfiddle.net/HB7LU/2844/ which is essentially the same thing just without the route information.
var myApp = angular.module('myApp',[]);
myApp.directive("exampleDirective", function() {
return {
restrict: "A",
scope: {
item: "="
},
template: "<div>{{ item }}</div>"
};
});
function MyCtrl($scope) {
$scope.item = 'Superhero';
}
With the view:
<div ng-controller="MyCtrl">
<div example-directive item="item"></div>
</div>
This leads me to believe it could be a scope issue. So try encapsulating the controller scope variable in a container:
$scope.cont = { item: 'x' };
And in the view
<div example-directive item="cont.item"></div>