I have a directive that only loads a template like this:
app.directive("sidebar", function(RESOURCE_URL) {
return {
templateUrl: RESOURCE_URL + 'sidebar.html'
};
});
The HTML looks like this:
<sidebar ng-controller="sidebarCtrl" ng-show="ready"></sidebar>
And the controller:
app.controller('sidebarCtrl', function($scope, authService) {
$scope.service = authService;
$scope.ready = false;
$scope.user = {};
$scope.$watch('service.getUser()', function(value){
$scope.user = value;
$scope.ready = true;
});
});
Is there a way to simply make the directive use the controller's scope variables? Or what's the common method to use in this case?
There are 2 main ways to access some scope variables inside your directive,
the first one is to enable scope inheritance inside of your directive by using scope: true
app.directive("sidebar", function(RESOURCE_URL) {
return {
scope: true,
templateUrl: RESOURCE_URL + 'sidebar.html'
};
});
which allows you to inherit outer scope inside of your directive, the other way is to attach the controller to your directive by using controller: sideBarCtrl:
app.directive("sidebar", function(RESOURCE_URL) {
return {
controller: 'sideBarCtrl',
templateUrl: RESOURCE_URL + 'sidebar.html'
};
});
or, you can write a service to hold your scope variables and this will allow you access from different parts of your code to the same instance of the variable.
https://docs.angularjs.org/guide/services
Related
What is the better approach to get access to scope created by ng-if in my directive (without $parent.params.text):
<span ng-if="params" uib-tooltip="{{params.text}}"></span>
.directive('myDirective', functions(){
return {
templateUrl: 'template.html',
replace: true,
$scope: {
data: '='
},
controller: function(){
if (data) { //some logic
$scope.params.text = 'text'
}
}
}
})
I've noticed that I don't have to use $parent if my variable is nested inside an object.
For example:
controller
$scope.params = { ... }
view ng-if="params"
Won't work, but:
controller
$scope.something_here = {};
$scope.something_here.params = { ... }
view ng-if="something_here.params"
would work. I believe Angular preserves the scope if the key you're trying to access is part of an object. Give it a try!
You could use the controllerAs syntax.
add
controllerAs: "vm",
bindToController: true
as properties in your directive definition and replace $scope with vm.
Then refer to vm.params.text inside the ng-if
I have a controller used to add tasks. On that page a user needs to select a group to act upon. I have written a directive that is used to allow a user to pick groups (folders)
My page controller
function AddTaskController($scope) {
var vm = this;
vm.group = { whatsit: true };
$scope.$watch("vm.group", function () {
console.log("controller watch", vm.group);
},true);
}
The page html where the directive is used
<em-group-selection group="vm.group"></em-group-selection>
The directive configuration
function GroupSelectionDirective() {
return {
scope: {
group: '='
},
controller: GroupSelectionDirectiveController,
controllerAs: 'vm',
templateUrl: '/views/templates/common/folderselection.html'
};
}
The directive controller:
function GroupSelectionDirectiveController($scope) {
var vm = this;
$scope.$watch("group", function () { console.log("yo1", vm.group); }, true)
$scope.$watch("vm.group", function () { console.log("yo2", vm.group); }, true)
}
Now when this fires, both console.log() calls in the directive fire once, with undefined. They never fire again. If in the controller I set vm.group to something else the $watch in the AddTaskController never gets fired.
Why isnt the data binding working?
Update:
I notice that if, in the directive, I change the init() function in my directive to use $scope it works! Can I not, as Fedaykin suggests, use controllerAs with two way data binding?
function init() {
$timeout(function () {
$scope.group.shizzy = 'timeout hit';
}, 200);
}
Turns out that if you use isolate scopes and controlelrAs syntax you need to also use bindToController : true. Without this you will not be able to only use vm and will have to use $scope for the isolate scope variables
More information can be found in the John Papa style guide and this SO answer
The final directive setup is as so:
function GroupSelectionDirective() {
return {
scope: {
group: '='
},
controller: GroupSelectionDirectiveController,
controllerAs: 'vm',
bindToController: true,
templateUrl: '/views/templates/common/folderselection.html'
};
}
Using Angular I created a directive like this:
angular
.module('my-module', [])
.directive('myDirective', function () {
return {
restrict: 'E',
templateUrl: currentScriptPath.replace('.js', '.html'),
scope: {
scenarios: '='
},
controller: MyDirectiveController,
controllerAs: 'vm',
bindToController: true,
replace: true
}
});
MyDirectiveController:
MyDirectiveController.$inject = ['$scope'];
function MyDirectiveController($scope) {
var vm = this;
vm.scenarios = $scope.scenarios;
}
My directive HTML template is this:
<div>{{vm.scenarios[0].name}}</div>
In my parent view HTML I'm using the directive this way:
<my-directive scenarios="vm.scenarios"></my-directive>
The parent controller has a property:
vm.scenarios = [] // could be [{ name : "test"}]
As the vm.scenarios of the parent controller gets set after an $http call it is not available when the vm.scenarios of the directive controller is bound to the $scope.scenarios and it doesn't get updated when the parents controller vm.scenarios gets populated eventually.
When adding this to my directives controller, it works but the solution seems wrong to me:
$scope.$watch('scenarios', function(newValue) {
if (newValue !== undefined) {
vm.scenarios = $scope.scenarios;
}
});
This is how you should define your directive controller:
MyDirectiveController.$inject = [];
function MyDirectiveController() {
// nothing here
}
You don't need to use $scope because you already bind to controller instance this. It means that scope config
scope: {
scenarios: '='
},
populates controller instance this object, not $scope object, and hence $scope.scenarios is undefined. With vm.scenarios = $scope.scenarios; in controller you just overwrite correct binding with undefined value.
Demo: http://plnkr.co/edit/lYg15Xpb3CsbQGIb37ya?p=preview
I know where the problem is i just don't know how to go about fixing it. I have two directives that call the same controller and after research i found out its a bad thing and i should use a service or something.
Now i believe i have to communicate between both these controllers. Every time i do a console.log inside the controller it runs twice.
What should i do?
Directives
app.directive("sidemenu", function() {
return {
restrict: 'A',
templateUrl: 'partials/sidemenu.html',
scope: true,
transclude : false,
controller: 'taskbarController'
}
});
app.directive("taskbar", function() {
return {
restrict: 'A',
templateUrl: 'partials/taskbar.html',
scope: true,
transclude : false,
controller: 'taskbarController'
}
});
Controller:
app.controller("taskbarController", ['$scope', 'authData', '$location', 'projectsModal', 'sendMessageModal', 'Poller',
function ($scope, authData, $location, projectsModal, sendMessageModal, Poller) {
$scope.inbox = Poller.msgdata;
$scope.project = Poller.newdata;
$scope.projects = Poller.projects;
$scope.messages = Poller.messages;
console.log($scope.inbox);
$scope.sendMessage = sendMessageModal.activate;
$scope.showModal = function() {
projectsModal.deactivate();
projectsModal.activate();
};
$scope.logout = function () {
authData.get('logout').then(function (results) {
authData.toast(results);
$location.path('login');
});
}
authData.get('session');
$scope.toggle = function(){
$scope.checked = !$scope.checked
projectsModal.deactivate();
sendMessageModal.deactivate();
}
}]);
You could still use controller (rather then service) as long as you are using it to bind the view (for e.g.) If you want to make a webservice call (for e.g.) then I would use service.
Thing you need to think about is that do you need two directive to share same service or just scope? If they (directive) are functionally different then use separate service/contr (Single Responsibility Principal) and if they have some shared data/scope then think about how to cater for that.If you want to share scope between controllers then you can use service which gets injects into the controller.
I'm using AngularUI router (0.2.13) and have a state defined as such
.state('foo', {
template:'<div some-directive></div>',
resolve: {
foo: function(SomeService) {
return SomeService.something().promise;
}
}
})
and a directive like this:
app.directive('someDirective', function(){
return {
controller: function(data) {
// I want `data` to be injected from the resolve...
// as it would if this was a "standalone" controller
}
}
})
but this doesn't work - the data parameter causes an UnknownProvider error. Defining the directive controller independently and setting it by name in the directive has the same result.
I more or less get why this is happening, but have two questions:
is there a way to do what I'm trying to do?
should I be trying to do it, or have I slipped into an antipattern here?
You can't use resolves in directives, but you can pass the result resolved in the state down to the directive which I think accomplishes what you're looking for.
You'd want to update your state definition to include a controller and set a parameter on the directive:
.state('foo', {
template:'Test<div some-directive something="foo"></div>',
url: 'foo',
resolve:{
foo:function(SomeService){
return SomeService.something();
}
},
controller: function($scope, foo){
$scope.foo = foo;
}
})
Then update the directive to use this parameter:
.directive('someDirective', function(){
return {
controller: function($scope) {
// I want `data` to be injected from the resolve...
// as it would if this was a "standalone" controller
console.log('$scope.something: '+ $scope.something);
},
scope: {
something: '='
}
};
})
Here's a sample plunker: http://plnkr.co/edit/TOPMLUXc7GhXTeYL0IFj?p=preview
They simplified the api. See this thread:
https://github.com/angular-ui/ui-router/issues/2664#issuecomment-204593098
in 0.2.19 we are adding $resolve to the $scope, allowing you to do "route to component template" style
template: <my-directive input="$resolve.simpleObj"></my-directive>,