How I can get element with class .second from second directive in link function of first directive?
http://plnkr.co/edit/PiCyZzgvdwAuOyNKOi9E?p=preview
P.S it works with template in link functions, but I need templateUrl.
Since you are using templateURL, the HTML need to be "downloaded" and then it is an asynchronous event which takes some time.
You could work around that with a timeout, but that wouldn't be clean as you don't know how long to wait.
One option, if <second> is supposed to always be inside <first>, is to run a callback from it once it is ready, from its own link function:
{
restrict: "E",
templateUrl:'second.html',
link: function(scope) {
scope.onSecondReady();
}
}
Check this plunkr for the full code:
http://plnkr.co/edit/NLdWaL8zRYkroGC7ZkYL?p=preview
Now if <second> is supposed to be re-usable in different context, and not always within the first one, then you need to use events. Once second is ready, you use scope.$emit('second-is-ready') and from the first one you listen to it scope.$on('second-is-ready')
Related
What I think I want to do is completely isolate each step of a wizard into a custom element directive.
I think this would allow me to completely encapsulate the detail of each page of the wizard. For example:
<custom-wizard-tag>
<enter-name-page page="1" name-placeholder="name"/>
<enter-address-page page="2" name-placeholder="name" address-placeholder="address" last-page/>
</custom-wizard-tag>
So far, so good. Each of the elements above has its own directive, and each of these specifies a templateUrl and a controller (templateUrl could be supplied as an attribute, of course).
I want each page of the wizard to 'inherit' some behaviour. The UI components would contain the buttons, which would need to query the outer scope, for example to determine whether it is possible to move forward, backward and so on. We would also need to call member functions on the parent scope in order to actually move the wizard forwards and backwards, and to check whether the current page number matches 'ours'.
I'm new to this so bear with me...
I read the documentation on directive, and thought I could use scope: { onNext: '&onNext' } in order to 'inherit' the onNext function from the previous scope (which is assumed to be one which is 'wizard-like'). However, this is not what angular seems to do. It seems to want map the inner scope's onNext via an attribute called on-next, thus breaking encapsulation, because now the UI elements must reference functions in the parent scope - which is exactly what I wanted to avoid.
Am I barking up the wrong tree, or is there an idiomatic way to do this. A day of web searching has not got me far, but I could be using the wrong search terms.
Thanks for your patience.
scope: { onNext: '&onNext' }
won't do any inherintance, you would have to define onNext in the template (the template scope) the same way you do with the page property: <enter-name-page page="1"
If you have a function onNext defined in you customWizardTag directive either in link function or in its controller, you'll have to put it in the controller, because the controller can be passed to the child directive. Then you'll be able to pass the parent directive's controller in the link functions of somethingPage directives.
.directive('parentDirective, function() {
return {
controller: someControllerThatHasOnNext,
}
})
.directive('childDirective', function() {
return {
require: '^^parentDirective',
link: function(scope, element, attrs, theParentDirectivesController){
theParentDirectivesController.onNext();
}
}
})
If this is what you wanted
In my angular app I've got a parent directive, say a list of some sort, that contains many child directives, say items.
I'd like to be able to call a certain method on the children from the parent. I know in advance what children I'd be calling (by an input of start and end index of the items list). Therefore I wish to avoid using broadcast, as I don't want to call all children but just a selected few.
Both the children and the parent have their own isolated scopes.
How can I achieve this?
Try to create controllers for your parent directive. That is accessible for your children directives, it's some kind of shared functions for your children directives. Then you can register a function which can add new functions to the parent controllers that u can later execute.
Let me show you with a quick example (maybe it won't work with copy-paste, its just the theory how should it look like)
//parent directive's controller function
function ParentControllerFunction(){
this.arrayOfChildFuncions = [];
}
//your child directive's link function
require: '^ParentControllerFunction'
link: function(scope, element, attr, ctrl){
function myLittleFunction(){
//hhere is your function that you can call
}
ctrl.arrayOfChildFuncions.push(myLittleFunction);
}
Then later on you can execute your functions depending on which one do you want to:
//executes the 3rd directive's function with the parameter 'hello'
arrayOfChildFuncions[3]('hello')
you can use $index in order to get the desired directive from ng-repeat, and then call your function.
https://docs.angularjs.org/api/ng/directive/ngRepeat
You have probably heard it as many times as I have. "Do all your DOM manipulation in directives". But no one ever seems to say what could happen if you actually do DOM manipulation outside a directive in Angular.
I have a problem that I managed to reproduce in this Plunk
I have made a very simple directive that just outputs the element to the console.
app.directive('dirre', function(){
return {
link: function(scope, element, attrs){
console.log({message:"dirrens linkFn", element: element, count: element.length})
}
}
});
I have two identical jquery UI accordions, the only difference is the way they are called. One is called in a controller and the other one in a directive. Calling accordion from a controller is of course something bad.
As you can see if you run the application there is a situation where one of the dirre-directives does not seem to have an element but there are no errors.
The same thing happens in a big application I'm working with right now. The problem seems to be that someone in our team decided to call Jquery UI's accordion in a controller and not in a directive.
I haven't been able to step through the code to see what actually happens but I strongly suspect that the DOM is modified while Angular is compiling and something goes wrong.
Is this a plausible explanation?
Is this an example of what can go wrong if you do DOM manipulations outside a directive?
The controller and the directive links function are called asynchronously.
Usually you can see directives being built before the main Controller complete. When the controller terminates, the directives update their watched variable (ngModel, $watch(something)...). Basically this is done with promises.
The link/compile function however is not called again. You have to compile, watch, apply the new DOM. Which basically means writing the similar code to angularjs.
In my angular app, directives are working fine during the first visit, but once a page been visited twice, all the directive link function gets called twice too. Say I am on page A, click a link to go to page B and then back to page A, all directives on page A will execute the their link function twice. if I refresh the browser it will become normal again.
Here is an example where the console.log will output twice when the second visit.
#app.directive 'testChart', ["SalesOrder", (SalesOrder) ->
return {
scope: {options: '='}
link: (scope, elem, attrs) ->
console.log("............checking")
SalesOrder.chart_data (data) ->
Morris.Line
element: "dash-sales"
data: data
xkey: 'purchased_at'
ykeys: ['total']
labels: ['Series a']
}
]
Any idea?
Update
My Route
when("/dash", {
templateUrl: "<%= asset_path('app/views/pages/dash.html') %>",
controller: DashCtrl
}).
so my chart is duplicated
also make sure you are not including your directive in your index.html TWICE!
I had this exact same problem.
After a loooooong time digging around I found that I hadn't used the correct closing tag which resulted in the chart being called twice.
I had
<line-chart><line-chart>
instead of
<line-chart></line-chart>
The link() function is called every time the element is to be bound to data in the $scope object.
Please check if you are fetching data multiple times , via GET call. You can monitor the resource fetching via Network tab , of chrome debugger.
A directive configures an HTML element and then updates that HTML subsequently whenever the $scope object changes.
A better name for the link() function would have been something like bind() or render(), which signals that this function is called whenever the directive needs to bind data to it, or to re-render it.
Maybe this will help somebody...
I had a problem with directive transclude, I used a transclude function which was adding child elements and also at the same time I forgot ng-transclude in directive template. Child elements were also directives and their link function was called twice!
Spent some time on this one..
More in details:
I had a "main" directive and "child" directives, idea was to use one inside another, something like that:
main
child
child
So problem was that link of "child" directive was called twice, and I didn't understand why,
Turned out I had ng-transclude in "main" directive template (I am posting it as it is in PUG format, sorry for that):
md-card(layout-fill)
md-card-content(flex)
.map-base(id="{{::config.id}}", layout-fill)
ng-transclude
and also in link function of "main" directive I called transclude function:
link: function($scope, $element, $attrs, controller, transcludeFn) {
$element.append(transcludeFn());
}
I think I just tried different combinations and forgot about that, visually everything was ok, but link was called twice and code was running twice and logic was broken..
So problem is that you can't have both and you have to choose one of the ways.
Hopefully now it is more clearer.
In my case I had a main-nav and sub-nav that both called a directive by its name attribute. Since the first instance already set the scope needed the second sub-nav the 2nd call wasn't needed. Incase anyone has a similar issue.
I'm trying to implement a Angular UI Bootstrap carousel, but I'm using it for a Quiz. Therefore, I don't need normal Prev() and Next() buttons.
Rather, I need a custom Next() button that makes sure they've selected an answer before continuing on to next "slide" of question/answers.
How do I hook into the carousel directive functions to run my code and then use the carousel.next() function?
Thanks,
Scott
There is no official possibility to achieve this. but this can be hacked, if you want. But i think it is better grab the bootstrap original one, have a look the at angular bootstrap ui sources (carousel) and write your own wrapper.
Here comes the hack:
The first problem we have to solve is, how to access the CarouselController. There is no API that exposes this and the carousel directive creates an isolated scope. To get access to this scope wie need the element that represents the carousel after the directive has been instantiated by angular. To achieve this we may use a directive like this one, that must be put at the same element as our ng-controller:
app.directive('carouselControllerProvider', function($timeout){
return {
link:function(scope, elem, attr){
$timeout(function(){
var carousel = elem.find('div')[1];
var carouselCtrl = angular.element(carousel).isolateScope();
var origNext = carouselCtrl.next;
carouselCtrl.next = function(){
if(elem.scope().interceptNext()){
origNext();
}
};
});
}
};
});
We must wrap our code in a $timeout call to wait until angular has created the isolated scope (this is our first hack - if we don't want this, we had to place our directive under the carousel. but this is not possible, because the content will be replaced). The next step is to find the element for the carousel after the replacement. By using the function isolateScope we have access to the isolated Scope - e.g. to the CarouselController.
The next hack is, we must replace the original next function of the CarouselController with our implementation. But to call the original function later we have to keep this function for later use. Now we can replace the next function. In this case we call the function interceptNext of our own controller. We may access this function through the scope of the element that represents our controller. If the interceptNext returns true we call the original next function of the carousel. For sure you can expose the complete original next function to our controller - but for demonstration purposes this is sufficient. And we define our interceptNext function like this:
$scope.intercept = false;
$scope.interceptNext = function(){
console.log('intercept next');
return !$scope.intercept;
}
We can now control the next function of the carousel by a checkbox, that is bound to $scope.intercept. A PLUNKR demonstrates this.
I knew this is not exactly what you want, but how you can do this is demonstrated.
That hack is neat michael, I started working on something similar for my needs. But then realized I might as well finally dip my toe into contributing to the open source community.
I just submitted a pull request to update the library so the index of the current slide is exposed to the Carousel scope.
https://github.com/angular-ui/bootstrap/pull/2089
This change allows you to have per-slide behavior in the carousel template.
This change allowed me to override the base carousel template so that for instance on the first slide the "prev" button would not show or the "next" button would not show for the final slide.
You can add more complex logic for your own personal needs, but exposing the current index in this manner to the $scope is part of making this part of the framework more flexible.
EDIT
I made more changes for my personal use, but don't want quite yet to contribute this change which is closer to what you are needing.
I modified the carousel directive, adding the "finish" property to scope.
.directive('carousel', [function () {
return {
restrict: 'EA',
transclude: true,
replace: true,
controller: 'CarouselController',
require: 'carousel',
templateUrl: 'template/carousel/carousel.html',
scope: {
interval: '=',
noTransition: '=',
noPause: '=',
finish: '='
}
};
}])
Then, when I declare the carousel, I can pass in a method to that directive attribute which is a method in the scope of the controller containing the carousel.
<carousel interval="-1" finish="onFinish">
...
</carousel>
This allows me to modify my template to have a button that looks like this:
<button ng-hide="slides().length-1 != currentIndex" ng-click="finish()" class="btn next-btn">finish<span class="glyphicon glyphicon-stats"></span></button>
So it only shows conditionally on the correct slide and with ng-click it is calling the carousel's $scope.finish() which is a pointer to a method in the controller I created for this application.
Make sense?
edit: This only works if you don't use sort functionality with ng-repeat. There is a bug which breaks the indexing of the slides for this kind of functionality.