angular directives - Parent calls children isolated scope method - angularjs

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

Related

Decoupling UI and Controllers in a nested custom directive

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

Directive to directive communication?

I have a page with two directives.
1. Directive that lists items , these items have a property on them call url
2. a directive on the same page that accepts url and displays it
What I want to do is, when the user clicks on one of the items in the first directive, to send that item.url to the 2nd directive as a parameter ?
What is the best practices for this scenario ?
Few ways to achieve this:
Common Parent Scope Property
Specifically the parent controller encompassing both of your directives has a property for storing the selected URL.
Then this property is passed into both of your child directives.
e.g.
<parent>
<directive-one data-selected-url="selectedUrl"></directive-one>
<directive-two data-url-to-display="selectedUrl"></directive-two>
</parent>
Broadcast Events
Have directive one broadcast an event on a scope that is shared by both directives.
e.g.
in directive one:
$scope.$broadcast('urlSelected', selectedUrl);
in directive two:
$scope.$on('urlSelected', function(event, selectedUrl) { ... });
NOTE: as I mentioned the scope needs to be a shared scope between the two directives, as broadcast sends the event DOWN the scope chain ($emit sends it up).
Accessing Parent Controllers
You could store the selected Url in a parent directive and have both children require that directive. They would then both be able to set/get the property from the parent directive.
e.g. in the child directives:
require: 'parentDirective'
link: function (scope, element, attrs, parentCtrl) {...}

AngularJS - Access a parent controller from child -> child directive with the controllerAs notation

I'd like to access a function from the parent => parent controller inside my directive (with the controllerAs notation). I'm using angular 1.3.14
I have the following structure:
Controller (with save function)
(Child) controller
Directive with a template (and isolated scope). Inside this template I have a button which should call the save function from the parent (parent) controller.
I don't want to call $scope.$parent.$parent.save(...)
Does anyone has an idea?
Thanks in advance.
Use just $scope.save().
In Angular there is scope hierarchy and by calling $scope.save() directive will look in directive's scope, if there is no save() method, it will look in parent's scope and so on.
One condition is to don't have isolated scope for directive.
There is no good way to do this other than passing the desired function into the directive. This is what using & in an isolated scope declaration is for.
https://docs.angularjs.org/guide/directive
Of course there are easier ways if the function is just a utility function. You could just register a filter. But if you want the child directive to alter the state of the parent controller at some event then using & is the best solution.
If you need the child directive to pass arguments to the function that is being passed to it then you are going to need to use the feature associated with this sentence in the above documentation:
This is specified in the directive by calling close({message: 'closing
for now'}). Then the local variable message will be available within
the on-close expression.

Angular directive link function called twice

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.

Directive requiring a parent

I need a directive ngTree to get the controller of a parent ngTree element. Note that require: "^ngTree" would return the controller of current directive.
I know I could do a ngTreeHelper and put a div with it, between any two ngTree divs and require: "^ngTreeHelper" would work, but it's ugly. I wonder if there is something else I could do.
There's not a great built in way to do this. However it is done in angular's form directive.
Swiping the code from there, this is what you need
var ngTreeController = $element.parent().controller('ngTree');
This is in the form directive's controller, but you should be able to do this from a link function too.

Resources