Lets say I have this directive. I want to change variable of directive scope from inside of parent controller. Is it possible? Or Is this the right way to perform what I am trying to achieve.
<div ng-show="devices.autocompletelist.length > 0" auto-complete url="/search" model="device.name" template-url="/themes/dashboard/assets/js/angular/filter/views/autocomplete-template.html" filtertype="device" autocompletelist="devices.autocompletelist"></div>
Directive
angular.module('autocomplete.directives',[]).directive('autoComplete',['$http',function($http){
return {
scope:{
selectedTagsData:'=model',
autocompletelist: '='
},
restrict: 'AE',
templateUrl: function(elem, attrs) {
return attrs.templateUrl
},
link: function(scope, elem, attrs) {
// I want to access and change value of selectedTagsData.data from parent controller
scope.selectedTagsData = {
data: ''
}
},
}
}]);
App
var app = angular.module('filterlist', ['autocomplete.directives']);
Controller
app.controller('filterCtrl', ['$rootScope', 'filterService', '$scope', '$compile', '$location', '$http', '$timeout', '$q',
function($rootScope, filterService, $scope, $compile, $location, $http, $timeout, $q) {
$scope.deviceSelect = function(devicedata) {
// Can I access and change value of directive scope variable 'scope.selectedTagsData.data' from here
}
}
]);
if you don't define scope in your directive, then all the $scope controller will be accessed through you directive, but this is not perfect solution, because you do not follow the separation of concern and Single Responsibility principle,
To summarize: your directive should not know anything about the parent scope and your controller should not know anything about a directive's internals. They are separate components in separate layers.
I think if you use transclude option it will solve your problem because transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside.
Related
I made custom directive that use Angular UI typeahead directive inside, but not working as expected. In my directive model is not updating on select. Anybody helps with that ? For testing i used static array instead of http service. Plunker HERE.
.directive('httpDictionary', ['$compile', function($compile){
return {
scope: {
},
restrict: 'A',
controllerAs: "dm",
controller: ['$scope', '$http', 'ARRAY', function($scope, $http, ARRAY){
var dm = this;
dm.dict = function(val){
return ARRAY; // for testing only
// return $http.get($scope.dictionaryUrl, { ...
}
}],
link: function(scope, element, attributes, ngModel) {
scope.dictionaryUrl = attributes.httpDictionary;
element.removeAttr('http-dictionary'); // avoid loop
element.attr('uib-typeahead', 'd for d in dm.dict($viewValue)');
$compile(element)(scope);
}
};
}])
To help with binding to a model, I usually use uib-typeahead setting typeahead-on-select. That way, you can check against or execute some additional code when the new model is set.
I made a plunker off your code found here but made some small tweaks:
I isolated the input text field into its own view so as to separate the functionality from the parent view.
I passed the test model to the directive's scope attribute and then bound it to the directives controller (using bindToController attribute) so if you ever needed the test model to communicate with the parent controller in the future, you could do so.
Hopes this helps.
I made this works like below. Plunker code here. I've bind ngModel in directive scope and used typeahead-on-select callback function. I don't think so this is elegant, but works. I've been thinking about using $watch, but without success. If You have a better soultion, i'll be glad.
.directive('httpDictionary', ['$compile', function($compile){
return {
scope: {
ngModel: '='
},
restrict: 'A',
controllerAs: "dm",
controller: ['$scope', '$http', 'ARRAY', function($scope, $http, ARRAY){
var dm = this;
dm.dict = function(val){
return ARRAY; // for testing only
// return $http.get($scope.dictionaryUrl, { ...
}
dm.select = function($model) {
$scope.ngModel = $model;
}
}],
link: function(scope, element, attributes, ngModel) {
scope.dictionaryUrl = attributes.httpDictionary;
element.removeAttr('http-dictionary'); // avoid loop
element.attr('uib-typeahead', 'd for d in dm.dict($viewValue)');
element.attr('typeahead-on-select','dm.select($model)');
element = $compile(element)(scope);
}
};
}])
AngularJS v1.2.28
Here is my sidebar controller:
angular.module('core').controller('SidebarController', ['$scope', '$location',
function($scope, $location) {
$scope.isItemActive = function (section) {
return ($location.path() === section);
};
}
]);
And sidebar's template:
<section data-ng-controller="SidebarController">
<a ui-sref="dashboard.new-subscription"
ui-route="/dashboard/subscription/new"
class="item "
ng-class="{active: isItemActive('/dashboard/subscription/new')}">
+ New Subscription
</a>
<subscriptions-list></subscriptions-list>
</section>
Subscription list's directive:
angular.module('core').directive('subscriptionsList', ['Subscription', '$state', '$location',
function(Subscription, $state, $location) {
return {
templateUrl: '/modules/core/views/subscriptions-list.client.view.html',
restrict: 'E',
link: function postLink(scope, element, attrs) {
....
scope.isItemActive = function(path, subscription) {
var curPath = path + '/' + subscription._id;
return ($location.path() === curPath);
};
}
};
}
]);
As you can see, both controller and directive have function isItemActive.
For some reason, when I call isItemActive('/dashboard/subscription/new') in sidebars template, I got error "Cannot read property '_id' of undefined" because it calls isItemActive() from subscriptionsList directive instead controllers directive.
How is that possible? How method from a directive can be accessible outside of directive's scope? I haven't used binding for this method..
Your directive doesn't use an isolated scope and doesn't create a child scope. The scope you use to declare isItemActive inside your directive is actually the scope the directive is used in, which is the same as the controller's scope. You basically override your controller's isItemActive.
You need to either use an isolated scope using scope: {} or create a child scope using scope: true.
You can read more about the differences here: https://github.com/angular/angular.js/wiki/Understanding-Scopes
If a directive is using a controller directly, why is calling a method on the controller by referring the controller by its alias, not doing anything?
Imagine we have the following piece of code:
var app = angular.module('App', []);
app.controller('MyController', ['$scope', function($scope) {
$scope.doAction = function() {
alert("controller action");
}
this.doAction2 = function() {
alert("controller action 2");
}
}]);
app.directive('myDirective', [function() {
return {
restrict: 'E',
scope: {},
controller: 'MyController',
controllerAs: 'myCtrl',
bindToController: true,
template: "<a href='#' ng-click='myCtrl.doAction()'>Click it!<a><br><a href='#' ng-click='myCtrl.doAction2()'>Click it #2!<a> " ,
link: function($scope, element, attrs, controller) {
console.log($scope);
}
}
}]);
While the first link won't work, the second will. To make the the first one work, I'd have to drop the alias, i.e. instead of calling the action by ng-click='myCtrl.doAction()' to call it as: ng-click='doAction()'
Shouldn't it work using the alias too? I mean, you are much more likely to find and reuse a controller, where the developers have attached actions to the $scope object and not to this
ControllerAs exposes the controller instance on the scope under $scope[alias].
In your example, the scope looks (conceptually) like this:
$scope = {
$id: 5,
myCtrl: {
doAction2: function(){...}
},
doAction: function(){...}
}
So, you can see why ng-click="myCtrl.doAction()" doesn't work.
The Controller-As approach has some benefits over directly exposing properties on the scope - one is that it does not pollute the scope (and its descendants) with properties that they may not need. It also inherently provides the dot-approach (.) to work properly with ng-model. You can find more information in this SO question/answer.
If I have a directive that uses a controller:
angular.module("module")
.controller("fooController", ["$scope", function($scope) {
...
})
.directive("foo", function() {
return {
restrict: "E",
controller: "fooController",
link: function($scope, $element, $attrs) {
// Do some things with the scope of the controller here
}
}
})
And multiple instances of that directive:
<foo></foo>
<foo></foo>
How many instances of the controller will be created?
Does it depend on whether the directive defines new child or isolated scope?
One controller instance will be created for each <foo> element defined in the DOM. So in the example given, there will be 2 controller instances created.
This does not depend on whether the directive defines a new child or isolated scope.
I'm trying to pass in the url for the template via a scope variable. The scope will not change so the template doesn't need to update based on it, but currently the scope variable is always undefined.
<div cell-item template="{{col.CellTemplate}}"></div>
Ideally the directive would be:
.directive("cellItem", ["$compile", '$http', '$templateCache', '$parse', function ($compile, $http, $templateCache, $parse) {
return {
scope: {
template: '#template'
},
templateUrl: template // or {{template}} - either way
};
}])
This doesn't work however. I've tried a lot of different permutations in accomplishing the same concept, and this seems the closest, however it still doesn't work.
.directive("cellItem", ["$compile", '$http', '$templateCache', '$parse', function ($compile, $http, $templateCache, $parse) {
return {
scope: {
template: '#template'
},
link: function (scope, element, attrs) {
var templateUrl = $parse(attrs.template)(scope);
$http.get(templateUrl, { cache: $templateCache }).success(function (tplContent) {
element.replaceWith($compile(tplContent)(scope));
});
}
};
}])
I've also tried using ng-include, but that also doesn't evaluate scope variables before compiling. The CellTemplate value is coming from a database call so is completely unknown before evaluation. Any suggestions for getting this working would be greatly appreciated!
Edit:
I'm using angular 1.0.8 and am not able to upgrade to a newer version.
You are not far off at all.
You don't need to use an isolated scope for the directive. You can pass the templateUrl like this:
<div cell-item template="col.CellTemplate"></div>
Then add a watch to detect when the template value changes:
.directive("cellItem", ["$compile", '$http', '$templateCache', '$parse', function ($compile, $http, $templateCache, $parse) {
return {
restrict: 'A',
link: function(scope , element, attrs) {
scope.$watch(attrs.template, function (value) {
if (value) {
loadTemplate(value);
}
});
function loadTemplate(template) {
$http.get(template, { cache: $templateCache })
.success(function(templateContent) {
element.replaceWith($compile(templateContent)(scope));
});
}
}
}
}]);
Here is a working Plunker: http://plnkr.co/edit/n20Sxq?p=preview
If you don't want to deal with the linking logic yourself, or you want the isolate scope, I think this is simpler:
.directive("cellItem", ["$compile", '$http', '$templateCache', '$parse', function ($compile, $http, $templateCache, $parse) {
return {
scope: {
template: '#template'
},
template: "<div ng-include='template'></div>"
};
}])
or:
template:"<ng-include src='template'></ng-include>"
It's an old post but I thought its useful if anyone lands in here for the answer.
You can try the templateUrl function as #caub mentioned in a comment. Same can also be used for components.
.directive("cellItem", ["$compile", '$http', '$templateCache', '$parse', function ($compile, $http, $templateCache, $parse) {
return {
templateUrl: function(element, attrs) {
return attrs.template || 'someDefaultFallback.html';
}
};
}]);
We don't need any of the injected dependencies here. Hope this helps someone.