I want to create a new directive into ui.boostrap.accordion module to avoid accordion open click event.
I have the following code in another file.js:
angular.module('ui.bootstrap.accordion')
.directive('accordionGroupLazyOpen', function() {
return {
require: '^accordion',
restrict: 'EA',
transclude: true,
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'template/accordion/accordion-group.html';
},
scope: {
heading: '#',
isOpen: '=?',
isDisabled: '=?'
},
controller: function() {
this.setHeading = function(element) {
this.heading = element;
};
},
link: function(scope, element, attrs, accordionCtrl) {
accordionCtrl.addGroup(scope);
scope.openClass = attrs.openClass || 'panel-open';
scope.panelClass = attrs.panelClass;
scope.$watch('isOpen', function(value) {
element.toggleClass(scope.openClass, value);
if (value) {
accordionCtrl.closeOthers(scope);
}
});
scope.toggleOpen = function($event) {
};
}
};
})
The problem is when I execute the app I get the following error:
Controller 'accordionGroup', required by directive
'accordionTransclude', can't be found!
Error link
Any ideas?
As I see from the source code ( maybe not your version but still the same):
// Use in the accordion-group template to indicate where you want the heading to be transcluded
// You must provide the property on the accordion-group controller that will hold the transcluded element
.directive('uibAccordionTransclude', function() {
return {
require: '^uibAccordionGroup', // <- look at this line in your version
link: function(scope, element, attrs, controller) {
scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
if (heading) {
element.find('span').html('');
element.find('span').append(heading);
}
});
}
};
So I guess it tries to find a parent directive in the view that matches accordionGroup but since you add the accordionGroupLazyOpen and not the accordionGroup it cannot find it.
In the error page you provided states:
This error occurs when HTML compiler tries to process a directive that
specifies the require option in a directive definition, but the
required directive controller is not present on the current DOM
element (or its ancestor element, if ^ was specified).
If you look in the accordion-group-template file you will see that the accordionTransclude directive gets called there.
Related
(function () {
'use strict';
angular.module('product')
.directive('sampledirective', ['$document') {
return {
restrict: 'E',
replace: true,
scope: {
data: '=',
btnClick: '&'
},
link: function (scope, element, attr) {
var compiled = $compile(template)(scope);
angular.element(element).replaceWith(compiled);
element = compiled;
};
};
}]);
})();
I have a directive which replaces the elements inside it.
I have a weird issue which replaces the elements mulitple time in the directive .
Duplicates the elements in the below bolded line which should not happen.
angular.element(element).replaceWith(compiled);
Please let me know why the elemenst are duplicated and let me know how to avoid it .
sample
Actual
cool cool
expected
cool
The following directive only replaces the content once in my case. If this dosn't solve your problem maybe you could provide a small working example or so. Also note that if you use an isolated scope for your directive you should provide a template, as stated in this post.
angular.module('product').directive("sampledirective", function ($compile) {
return {
template: '',
restrict: 'E',
scope: {
data: "=data",
btnClick: '&'
},
link: function (scope, element, attrs) {
var template = "<div>foo</div>"
var compiled = $compile(template)(scope);
element.replaceWith(compiled);
}
}
});
I have angular directive that looks like this
app.directive('paymentsTable', ['$watch', function($watch) {
return {
replace: true,
restrict: 'EACM',
templateUrl: '../views/paymentTable.html',
link: function(elem, attr, scope) {
console.log(elem.$parent.payments); // array
scope.$watch(function(elem) { return elem.$parent.payments }, function(value, oldValue) {
});
}
};
}]);
It gives me
angular.js:13920Error: [$injector:unpr]
When I rewrite first line like this
app.directive('paymentsTable', [ function() {
It gives me another error
angular.js:13920TypeError: o.$watch is not a function
I also use uglify. So, my question is: what is going on here?
The $watch function is part of scope that is handed to you in the link method, therefore there is no need to inject it. The reason you get the second error is the order of the link arguments. Try it like this:
app.directive('paymentsTable', function() { // no need for injection
return {
replace: true,
restrict: 'EACM',
templateUrl: '../views/paymentTable.html',
link: function(scope, element, attrs) { // The correct arguments order
console.log(elem.$parent.payments);
scope.$watch(function(elem) { return elem.$parent.payments }, function(value, oldValue) {
});
}
};
});
I'm quite new to AngularJS and I'm trying to understand a few things.
First of all, I have my controller of which I will place a snippet here:
var OfficeUIRibbon = angular.module('OfficeUIRibbon');
// Defines the OfficeUIRibbon controller for the application.
OfficeUIRibbon.controller('OfficeUIRibbon', ['$scope', '$http', function($scope, $http) {
var ribbon = this;
ribbon.setApplicationMenuAsActive = function() {
ribbon.applicationMenuActive = true;
}
}
Then I have a directive:
var OfficeUIRibbon = angular.module('OfficeUIRibbon');
OfficeUIRibbon.directive('ribbonApplicationMenu', function() {
return {
restrict: 'E',
replace: false,
scope: {
data: '#'
},
templateUrl: function(element, attributes) {
return attributes.templateurl;
}
}
});
The directive is called like this:
<ribbon-application-menu templateUrl="/OfficeUI.Beta/Resources/Templates/ApplicationMenu.html" data="/OfficeUI.Beta/Resources/JSon/Ribbon/ribbon.json"></ribbon-application-menu>
This does all work and in my template for the directive, the following is placed:
<div id="application" id="applicationMenuHolder" ng-controller="OfficeUIRibbon as OfficeUIRibbon" ng-show="OfficeUIRibbon.applicationMenuActive"...
From inside another element, when I execute a click a function on my controller is executed:
ng-click="OfficeUIRibbon.setApplicationMenuAsActive()"
Here's the directive from the other element:
OfficeUIRibbon.directive('ribbon', function() {
return {
restrict: 'E',
replace: false,
scope: {
data: '#'
},
templateUrl: function(element, attributes) {
return attributes.templateurl;
}
}
});
This function does change the property "applicationMenuActive" on the ribbon itself, which should make the item in the directive template show up.
However, this is not working. I'm guessing I need to watch this property so the view get's updated accordingly.
Anyone has an idea on how this could be done?
I have a directive which is transcluding it's content. And in the transcluded content is a directive which requires the controller of the transcluding directive. This throws an error if i'm creating a transclude function in the transcluding directive. I think this is because the transcluded content gets cloned when you provide a transclude function (https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L846).
I also have a plunker describing my problem: http://plnkr.co/edit/rRKWW6zfjZuUiw1BY4zs?p=preview
What i want to do is i want to transclude the content and parse all of the transcluded content and then put it in the right place in the DOM and compile it myself. The transcluded content is actually the configuration for my directive.
I've also tried emptying the cloned array i receive in the transcluding function, because i actually don't need the content to be transcluded automatically. I just need to parse it and transclude it manually on a later point in time. Angular doesn't need to do anything with my transcluded content. But this doesn't work because the directives are already identified when the transcluding function is called. So when i empty the array i receive an error here (https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L961).
kind regards,
Daan
When you use require: "^controller" you are telling Angular that the directive requires controller to be attached to an ancestor DOM element at the time the link function is run.
When you do transclusion without using the ngTransclude directive, your parent directive link function get's passed a transclude method. (You already knew that; this is just for completeness.) This transclude method does the following:
Extracts the content for transclusion
If a cloneAttachFn was passed in, clone the content and call the cloneAttachFn
Calls $compile() to compile and link the content (or cloned content) using the scope supplied by the call to transclude (defaults to a new scope that inherits from the $parent of the directive scope).
If you call transclude, and end up not attaching the content as a descendant of an element with the required controller (or not adding the content to the DOM at all), then the content will have no parent with the required controller. Because it can't find the required controller, you get an error.
In your example, if you use kmBar with require: "^kmFoo", you are restricted to adding the transcluded content to DOM nodes that are descendants of nodes that have kmFoo.
The easiest fix is to go ahead and append it to kmFoo's element for the purposes of $compile() and linking, but immediately detach it.
Detach (as opposed to remove) maintains click handlers, etc, so everything will continue to work if you append the element later. If you are using early versions of AngularJS, you may need to include jQuery to get detach since it wasn't included in early versions of jqLite.
Here's a snippet of a Plunk I put together
app.directive('kmFoo', function() {
return {
restrict: 'A',
scope: true,
template: '<div></div>',
transclude: true,
controller: function() {
// ...
},
link: function(scope, $element, attrs, ctrl, transcludeFn) {
console.log('linking foo');
// We are going to temporarily add it to $element so it can be linked,
// but after it's linked, we detach it.
transcludeFn(scope, function(clone) {
console.log('transcluding foo');
$element.append(clone);
c = clone;
}).detach();// <- Immediately detach it
}
};
});
app.directive('kmBar', function() {
return {
restrict: 'A',
scope: true,
require: '^kmFoo',
link: function(scope, $element, attrs, fooCtrl) {
console.log('linking bar');
// Right now it's a child of the element containing kmFoo,
// but it won't be after this method is complete.
// You can defer adding this element to the DOM
// for as long as you want, and you can put it wherever you want.
}
};
});
First of all, why do need the transclude function? Is this not sufficient?
app.directive('kmFoo', function() {
return {
'restrict': 'A',
'scope': true,
'controller': function() {
this.tryMe = function() { console.log("Success!") };
},
'link': function(scope, element, attrs, ctrl) {
console.log('linking foo');
var innerHtml = element.html();
// do something with innerHtml
element.html("<div>Empty</div>");
}
};
});
app.directive('kmBar', function() {
return {
'restrict': 'A',
'scope': true,
'require': '^kmFoo',
'link': function(scope, element, attrs, fooCtrl) {
fooCtrl.tryMe();
}
};
});
But if you really want to get access to the fooController and have a transclude function in kmFoo, you can access the controller via element.controller() after all linking is done and all controllers are initialized.
app.directive('kmFoo', function() {
return {
'restrict': 'A',
'scope': true,
'template': '<div ng-transclude></div>',
'transclude': true,
'controller': function() {
this.tryMe = function() { console.log("Success!") };
},
'link': function(scope, $element, attrs, ctrl, transcludeFn) {
console.log('linking foo');
// when you put the transclude function in comments it won't throw an error
transcludeFn(scope, function(clone) {
console.log('transcluding foo');
});
}
};
});
app.directive('kmBar', function() {
return {
'restrict': 'A',
'scope': true,
'template': "<button ng-click='tryMe()'>Feeling lucky?</button>",
'link': function(scope, element, attrs) {
scope.getFooCtrl = function() {
return element.parent().controller('kmFoo');
};
console.log('linking bar');
console.log('parent not yet known: ' + element.parent().toString());
},
'controller': function($scope) {
$scope.tryMe = function() {
$scope.getFooCtrl().tryMe();
};
}
};
});
See it in action with this plnkr.
To perform this without having it displayed in the DOM, you can use a
transclude: 'element'
on the second directive.
This will avoid using some tricks to get the information you need.
app.directive('kmFoo', function() {
return {
'restrict': 'A',
'scope': true,
'template': '<div ng-transclude></div>',
'transclude': true,
'controller': function() {
},
'link': function(scope, $element, attrs, ctrl, transcludeFn) {
console.log('linking foo');
// when you put the transclude function in comments it won't throw an error
//transcludeFn(scope, function(clone) {
// console.log('transcluding foo');
//});
}
};
});
app.directive('kmBar', function() {
return {
'restrict': 'A',
'scope': {},
'require': '^kmFoo',
'link': function(scope, $element, attrs, fooCtrl) {
console.log('linking bar');
}
};
});
app.directive('kmBarWithElement', function() {
return {
'restrict': 'A',
'scope': {},
'transclude': 'element',
'require': '^kmFoo',
'link': function(scope, $element, attrs, fooCtrl, transclude) {
transclude(function(clone) {
console.log('here the element: ', clone);
});
}
};
});
Here is a working example: http://plnkr.co/edit/lbT7oz74Yz7IZvEKp77T?p=preview
I have an angular directive which is initialized like so:
<conversation style="height:300px" type="convo" type-id="{{some_prop}}"></conversation>
I'd like it to be smart enough to refresh the directive when $scope.some_prop changes, as that implies it should show completely different content.
I have tested it as it is and nothing happens, the linking function doesn't even get called when $scope.some_prop changes. Is there a way to make this happen ?
Link function only gets called once, so it would not directly do what you are expecting. You need to use angular $watch to watch a model variable.
This watch needs to be setup in the link function.
If you use isolated scope for directive then the scope would be
scope :{typeId:'#' }
In your link function then you add a watch like
link: function(scope, element, attrs) {
scope.$watch("typeId",function(newValue,oldValue) {
//This gets called when data changes.
});
}
If you are not using isolated scope use watch on some_prop
What you're trying to do is to monitor the property of attribute in directive. You can watch the property of attribute changes using $observe() as follows:
angular.module('myApp').directive('conversation', function() {
return {
restrict: 'E',
replace: true,
compile: function(tElement, attr) {
attr.$observe('typeId', function(data) {
console.log("Updated data ", data);
}, true);
}
};
});
Keep in mind that I used the 'compile' function in the directive here because you haven't mentioned if you have any models and whether this is performance sensitive.
If you have models, you need to change the 'compile' function to 'link' or use 'controller' and to monitor the property of a model changes, you should use $watch(), and take of the angular {{}} brackets from the property, example:
<conversation style="height:300px" type="convo" type-id="some_prop"></conversation>
And in the directive:
angular.module('myApp').directive('conversation', function() {
return {
scope: {
typeId: '=',
},
link: function(scope, elm, attr) {
scope.$watch('typeId', function(newValue, oldValue) {
if (newValue !== oldValue) {
// You actions here
console.log("I got the new value! ", newValue);
}
}, true);
}
};
});
I hope this will help reloading/refreshing directive on value from parent scope
<html>
<head>
<!-- version 1.4.5 -->
<script src="angular.js"></script>
</head>
<body ng-app="app" ng-controller="Ctrl">
<my-test reload-on="update"></my-test><br>
<button ng-click="update = update+1;">update {{update}}</button>
</body>
<script>
var app = angular.module('app', [])
app.controller('Ctrl', function($scope) {
$scope.update = 0;
});
app.directive('myTest', function() {
return {
restrict: 'AE',
scope: {
reloadOn: '='
},
controller: function($scope) {
$scope.$watch('reloadOn', function(newVal, oldVal) {
// all directive code here
console.log("Reloaded successfully......" + $scope.reloadOn);
});
},
template: '<span> {{reloadOn}} </span>'
}
});
</script>
</html>
angular.module('app').directive('conversation', function() {
return {
restrict: 'E',
link: function ($scope, $elm, $attr) {
$scope.$watch("some_prop", function (newValue, oldValue) {
var typeId = $attr.type-id;
// Your logic.
});
}
};
}
If You're under AngularJS 1.5.3 or newer, You should consider to move to components instead of directives.
Those works very similar to directives but with some very useful additional feautures, such as $onChanges(changesObj), one of the lifecycle hook, that will be called whenever one-way bindings are updated.
app.component('conversation ', {
bindings: {
type: '#',
typeId: '='
},
controller: function() {
this.$onChanges = function(changes) {
// check if your specific property has changed
// that because $onChanges is fired whenever each property is changed from you parent ctrl
if(!!changes.typeId){
refreshYourComponent();
}
};
},
templateUrl: 'conversation .html'
});
Here's the docs for deepen into components.