I am new to angular bindToController when i am trying to map controller scope as reference to a directive it doesn't works.
Example AS:
app.controller('test',function($scope){
$scope.reftodir = $scope;
$scope.test = 'someconte';
})
in html referenced it as :
<test-directive testscope = 'reftodir' iso-scope="test"/>
app.directive('testDirective',function(){
return{
scope:{
isoScope: '='
},
bindToController: {
testscope:'='
},
}
})
Suppose i have a function in controller as test_function, i need to call that function from directive with the help controller scope available from bindToController variable such as testscope.test_function().
but test_function is not available. Please provide any suggestions, Thanks in advance friends.
Your directive object should look like this:
return {
controller: 'test',
bindToController: true,
controllerAs: 'vm'
scope: {
testScope: '='
}
}
then in your controller you should be able to see your scopes like this:
app.controller('test',function(){
this.testScope //scope is available here
})
note that we won't need $scope anymore if we bind to controller, and the this part is points to the controller it self
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
(function () {
'use strict';
angular
.module('app')
.directive('documentList', documentList);
documentList.$inject = ['$window'];
function documentList($window) {
var directive = {
restrict: 'E',
controller: controller,
controllerAs: "dl",
templateUrl: 'directives/document/document-list.html',
transclude: false,
scope: {
productRef: "=",
productSerialNumber: "=",
title: "#",
eid: "#"
},
};
return directive;
function controller($scope, $state, $element, documentService, ngProgressFactory, registrationService) {
var self = this;
self.goToDetailPage=goToDetailPage;
function goToDetailPage(docId) {
return "a";
}
})();
(function() {
'use strict';
angular
.module('app')
.controller('DetailCtrl', detailCtrl);
// Implementation of controller
detailCtrl.$inject = ['$scope', '$state', '$stateParams', '$rootScope'];
function detailCtrl($scope, $state, $stateParams, $rootScope) {
var self=this;
//alert($stateParams.docId)
self.a=$scope.dl.goToDetailPage();
}
})();
Above is the code in my directive and I have a controller where I want to call goToDetailPage function . But when I am trying to access it through var a=$scope.goToDetailPage() , I am getting error in console.
Not able to rectify.
Any help is appreciated!!!
Thanks
//DetailPage
.state('app.sr.detail', {
name: 'detail',
url: '/detail',
templateUrl: 'views/detail/detail1.html',
controller: 'DetailCtrl',
controllerAs: 'vm'
})
A cool pattern you can use when you want to be able to invoke a function that lives in a directive but you need to be able to invoke from your controller is to pass in an object using two way binding and then extend that object with a function inside the directive. Inside your directive pass in an additional value:
scope: {
productRef: "=",
productSerialNumber: "=",
title: "#",
eid: "#",
control: '=', // note that this is passed in using two way binding
}
Then extend that object inside your directive's controller by attaching a function to it:
// this is in your directive's controller
$scope.control.goToDetailPage = function() {
// function logic
}
Now define the control object in your Controller and pass it into the directive. Because it is two way bound the function will be applied and available to be called in either scope.
// in your controller, assuming controller as syntax
var vm = this;
vm.control = {};
// somewhere later you can invoke it
vm.control.goToDetailPage();
Maybe try,
$scope.goToDetailPage = function (docId) {
return "a";
}
Or, to make use of the "controller as" syntax,
var a = dl.goToDetailPage();
As it looks like from the snippets, DetailCtrl is parent of documentList directive. Functions in a child scope can't be accesses from parent controller. Consider defining goToDetailPage in DetailCtrl and inject that into the directive's scope using '&'
EDIT
If you have something like this:
<div ng-controller="DetailCtrl">
<document-list ...></document-list>
</div>
Then the controller DetailCtrl 's scope is the parent scope and the documentList directive's is the child. And since you are defining an isolate scope in documentList directive using scope: {...} parent and child scopes are different. And a method defined in the isolate scope can't be accessed directly in the parent scope using $scope.methodName().
You can however do the other way around, i.e. define the method in the DetailCtrl and inject into the directive's scope using this:
<div ng-controller="DetailCtrl">
<document-list .... detail-page="goToDetailPage()"></document-list>
</div>
And change your directive's scope to:
scope: { ...,
detailPage:'&'
},
Now you can make a call to the function in the directive controller by calling detailPage()
See the Angular Guide
Hope that helps
I'm aware how I can get parent's directive controller in the child directive's link function.
However, I'd prefer to avoid using link function (and $scope all-together) and have all my code under the controller function of the directive.
angular.directive('parent', function(){
return {
templateUrl: '/parent.html',
scope: true,
bindToController: true,
controllerAs: 'parentCtrl',
controller: function(){
this.coolFunction = function(){
console.log('cool');
}
}
}
});
angular.directive('child', function(){
return {
templateUrl: '/child.html',
require: '^parent',
scope: true,
bindToController: true,
controllerAs: 'childCtrl',
controller: function() {
// I want to run coolFunction here.
// How do I do it?
}
}
});
Any help is appreciated!
The proper pattern for that would be
app.directive('child', function(){
return {
templateUrl: '/child.html',
require: ['child', '^parent'],
scope: true,
bindToController: true,
controllerAs: 'childCtrl',
controller: function() {
this.coolFunction = function () {
this._parent.coolFunction();
}
},
link: function (scope, element, attrs, ctrls) {
var childCtrl = ctrls[0];
var parentCtrl = ctrls[1];
childCtrl._parent = parentCtrl;
}
}
});
The bad thing is that _parent is being exposed to scope with controllerAs, but it will rarely be a problem.
Notice that you won't have access to parent controller from child until link glues them together. Which is fine as long as you use parent controller in child methods.
Controller provides methods and initial properties to view model (and it does it cleaner with controllerAs), link glues the stuff, that's how directives work.
Both $scope and link have their purposes in Angular 1.x and are indispensable even with latest community developments. Banishing them for no valid reason is overzealous and may lead to bad design solutions. The absence of 'link' and 'scope' words in code won't help to make the app easier to port to 2.x. Though learning Angular 2 now and developing proper habits for 1.x will.
You could inject '$element' into the controller and access the parent controller like -
controller: ($element) ->
var parentCtrl = $element.parent().controller('parent');
parentCtrl.coolFunction();
//..........
//..........
This may not be the most transparent way of accessing 'any' parent controller because it requires the specific name of the directive and it is jqlite and not pure Angular.
Found this thread useful - How to access parent directive's controller by requiring it recursively?
EDIT: Thanks to #Dmitry for figuring out that angular doesn't need '.parent' to get the controller. Updated code -
controller: ($element) ->
var parentCtrl = $element.controller('parent');
parentCtrl.coolFunction();
//..........
See here or here
var parentForm = $element.inheritedData('$formController') || ....
var parentForm = $element.controller('form')
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
In angular.js, can a directive controller access data in a page controller that loaded it?
/**
* Profile directive
*/
.directive('profile', function () {
return {
restrict: 'E',
replace: true,
templateUrl: '/partials/users/_profile.html',
scope: {
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
$scope.show = angular.isDefined($scope.show) ? $scope.show : { follow: true, link: true };
$scope.currentUser = $rootScope.currentUser;
//do stuff here and then set data in UserShowCtrl
}
};
});
The <profile user="user"></profile> method is called from ./users/show.html which uses the UserShowCtrl controller.
Is there anyway I can use scope on the profile directive with its own controller and still be able to pass data to the UserShowCtrl?
Even though the profile can be isolated to its own functionality, it still needs to set some data on the page level in the UserShowCtrl controller.
Here is where _user.html is loading the <profile> directive. The data for the page is served by the UserShowCtrl and has some collections that get updated when things happen, like following a user.
<ol class="following" ng-show="showConnections == 'following'">
<li ng-repeat="following in user.following">
<profile user="connections[following]"></profile>
</li>
</ol>
Right now there is an ng-click="follow(user)"> that is happening in the _profile.html. I would like to be able to have the directive handle this but also update the collections in the UserShowCtrl.
Edit: here is a plunker demonstrating what I'm trying to do:
http://plnkr.co/edit/9a5dxMVg9cKLptxnNfX3
You need to use a service in order to share any information between controllers, directives, services
something like
angular.module('myapp',[]).
service('myservice',function(){
return {a:'A',b:'B'}
}).
controller('mycontroller',['myservice',function(myservice){
//do someting with myservice
}]).
directive('mydirective',['myservice',function(myservice){
//do someting with myservice
}]);
there controller and directive access the same data through the service
You can access the parent scope from your directive with $scope.$parent.myvar.
myvar will be resolved in parent scope, which means prototypical scope inheritance is used to resolve the variable.
However, this does not guarantee that myvar is coming from the same scope as UserShowCtrl since its possible that any scope in between the 'profile' directive and UserShowCtrl's scope may override 'myvar'.
A better solution would be to use directive-to-directive communication. There are generally two ways for directives to communicate:
Through attributes passed into your directive. You've already used this method to import 'user' and 'show' from parent scope into your directive's isolated scope.
Requiring another directive. When you use 'require: ^UserShow', you are specifying that your 'profile' directive requires another directive as a dependency. The '^' means that it will search for the directive on the current element, or any parent element further up the DOM tree. UserShow's controller is then passed to your link function:
.directive('UserShow', function () {
return {
restrict: 'E',
controller: function($scope){
$scope.myvar = 'test';
this.setMyVar = function(var) {
$scope.myvar = var;
}
}
};
});
.directive('profile', function () {
return {
restrict: 'E',
replace: true,
templateUrl: '/partials/users/_profile.html',
require: '^UserShow',
scope: {
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
},
link: function(scope, element, attr, UserShowCtrl) {
UserShowCtrl.setMyVar('hello world!);
}
};
});
HTML:
<user-show>
<profile>...</profile>
</user-show>
I am not quite sure what your after.
You are already having 2 two-way data bindings, which means that if you change user in your directive, that will also flow to the outside scope.
So you already have a solution in front of you...
So if that is not "good enough", there is something missing in your question.
Here is an illustration: http://plnkr.co/edit/qEH2Pr1Pv7MTdXjHd4bD?p=preview
However, if you use something in your outside template that creates a child scope, binding it as "value" there is NOT enough, you need to have a . in there.
But that is where there is missing something to the question, if you share your show.html I may be able to find where the scope breaks apart and explain why...
Relevant Source from demo.js:
app.directive('profile', function () {
return {
restrict: 'E',
replace: true,
template: '<div><input type="text" ng-model="user"></input></div>',
scope: { //defines an isolate scope.
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
$scope.show = angular.isDefined($scope.show) ? $scope.show : { follow: true, link: true };
$scope.currentUser = $rootScope.currentUser;
$scope.user = "Changed by scope!";
//do stuff here and then set data in UserShowCtrl
}
};
});
app.controller('UserShowCtrl', function($scope) {
$scope.value = "Value set outside!";
$scope.alertValue = function() {
alert($scope.value);
}
});
Relevant Source from home.html:
<div ng-controller="UserShowCtrl">
{{ value }}
<profile user="value"></profile>
<button ng-click="alertValue()">ALERT!</button>
</div>