How to trigger methods on my custom directive? - javascript

I have a custom directive that I'm using in my templates. It does a bit of DOM work for me. I would like the host view/controller that I'm using the directive in to be able to run methods on my directive (and it's controller). But I'm not sure how best to call into the directives scope.
Example Fiddle
My view code:
<div ng-app="app">
<div ng-controller="MainCtrl">
<h3>Test App</h3>
<button ng-click="scopeClear()">Parent Clear</button>
<div my-directive string="myString"></div>
</div>
</div>
Here is the custom directive:
angular.module('components', []).directive('myDirective', function() {
function link(scope, element, attrs) {
scope.string = "";
scope.$watch(attrs.string, function(value) {
scope.string = value;
});
}
return {
controller: function($scope, $element) {
$scope.reset = function() {
$scope.string = "Hello";
}
$scope.clear = function() {
$scope.string = "";
}
},
template:
"<button ng-click='reset()'>Directive Reset</button>" +
"<button ng-click='clear()'>Directive Clear</button><br/>" +
"<input type='text' ng-model='string'>",
link: link
}
});
And controller:
angular.module('app', ['components']).controller('MainCtrl', function($scope) {
$scope.myString = "Hello";
$scope.scopeClear = function() {
// How do I get this to call the clear() method on myDirective
}
});
The workaround I found is jQuery('#my_directive').scope().myMethod(); But this seems wrong, like I'm missing some better part of angular to do this.
It also seems like and $emit isn't right here since I want a targeted method so it won't trigger on additional instances of the directive I have on the same page.
How would I access the directives methods from my parent controller?

I'm not sure I fully understand your objective here, and it's possible you could find a better pattern completely. Typically, directives display the state of the scope which is either an isolate scope (if they are self-sufficient) or a shared scope. Since you are not creating an isolate scope then they inherit the scope from the controller. If they are displaying data inherited from the controller then you don't want your controller calling into the directive, rather the directive will simply "redraw" itself whenever the properties in the controller change.
If you, instead, are looking to recalculate some stuff in your directives based on events from outside the directive you don't want any tight coupling - especially if building an entirely separate module. In that case, you might simply want to use $broadcast from the $scope within MainCtrl to broadcast an event that you may care about, and then your directive can provide the $on('eventName') handler. This way it's portable to any controller/scope that will fire such an event.
If you find yourself needing to know the exact properties in the controller or the exact functions within the directive then I would suggest that you have too-tightly coupled these pieces and they don't belong in separate modules since they could never be reused. Angular directives and controllers are not objects with functions, but objects that create scope and update frequently via $digest calls whenever properties in that scope change. So you may be able to find a way to better model the data, objects, and properties you are displaying. But I can't say without greater context.

Related

Angular directives that call methods on child directives

I am looking for advice on how to implement a hierarchical structure in Angular, where a directive (<partition>) can call a method on a child directive's controller (<property-value>).
I have put together a detailed example here:
https://jsfiddle.net/95kjjxkh/1/
As you can see, my code contains an outer directive, <partition>, which displays one or more <property-value> directives within.
The <property-value> directive offers an editing method, editItem(), which allows the user to change the value of a single entry. (To keep my example short, I simply assign a random number here, but in my production app, a modal will appear, to query the user for a new value.)
This works fine. However, in the outer directive, <partition>, I would like to add the ability to create a new, blank <property-value> directive and then immediately call its editing method so that the user can enter an initial value. If no initial value is entered, the new item would be discarded.
I have seen examples of inner directives calling methods on enclosing directives, but not the other way around.
Is there a way to do this? Alternatively, is there a better way for me to build this kind of view?
You can always use $broadcast to talk both ways. To your parent as well as to your childrens.
In your Child controller you can do the following
app.directive('propertyValue', function() {
return {
require : '^partition'
restrict: 'E',
scope: {
item: '='
},
with this you will get the parent controller in child directive's link function like this
link:function(scope,element,attrs,partitionCtrl){
partitionCtrl.getChildCtrl(element)
}
in partition controller create getChildCtrl function and with that call "propertyvalue" controller function
controller: function ($scope, ItemFactory) {
// your code
var propValueCtrl =undefined;
this.getChildCtrl =function(elem)
{
propValueCtrl = elem.controller();
}
this.callChildFunction = function()
{
propValueCtrl.Edit();// whatever is the name of function
}
call this function when needed in property link function.
Hope this helps.

Are variable bound / 1st class functions preferable over private method naming? How is hoisting affected?

A few questions regarding structuring Angular code and the behavior of JavaScript when using variable bound vs private method naming function conventions. Is there a performance or stylistic reason for using variable bound functions / first class functions in AngularJS over private method naming? How is hoisting affected in each method? Would the second method below reduce the amount of hoisting performed and would this have a noticeable affect on application performance?
An example of private method naming. Is this a recommended way to structure Angular code?
(function () {
'use strict'
function modalController(dataItemsService, $scope) {
var vm = this;
function _getSelectedItems() {
return dataItemsService.SelectedItems();
}
function _deleteSelectedItems() {
dataItemService.DeleteItem();
$("#existConfirmDialog").modal('hide');
}
vm.SelectedItems = _getSelectedItems;
vm.deleteItemRecord = _deleteItemRecord;
}
angular.module('app').controller('modalController', ['dataItemService', '$scope', modalController]
})();
An example of variable bound functions. What about this method of structuring angular code within the controller - is there any disadvantage or advantage in terms of performance/style?
angular.module('appname').controller("NameCtrl", ["$scope", "$log", "$window", "$http", "$timeout", "SomeService",
function ($scope, $log, $window, $http, $timeout, TabService) {
//your controller code
$scope.tab = 0;
$scope.changeTab = function(newTab){
$scope.tab = newTab;
};
$scope.isActiveTab = function(tab){
return $scope.tab === tab;
};
}
]);
The first method, using "private" methods and exposing them via public aliases, is referred to as the Revealing Module Pattern, although in the example the methods aren't actually private.
The latter is a pretty standard Constructor Pattern, using $scope as context.
Is there a performance or stylistic reason for using variable bound functions / first class functions in AngularJS over private method naming?
Is [there] a recommended way to structure Angular code?
TL;DR
Fundamentally, there isn't much difference between the two styles
above. One uses $scope, the other this. One Constructor function is defined in a closure, one is defined inline.
There are scenarios where you may want a private method or value.
There are also stylistic and (probably insignificant) performance
reasons for using the variable this/vm over $scope. These are not mutually exclusive.
You'll probably want to use a
basic, bare bones, old school Constructor Pattern, and a lot of
people are exposing state and behavior via this instead of $scope.
You can allow yourself data privacy in the Controller, but most of
the time this should be leveraged by a Service/Factory. The main exception is data representative of the state of the View.
Don't use jQuery in your Controller, please.
References:
AngularJS Style Guide by Todd Motto.
AngularJS Up & Running
To answer your question thoroughly, I think it important to understand the responsibility of the Controller. Every controller's job is to expose a strict set of state and behavior to a View. Put simply, only assign to this or $scope the things you don't mind your user seeing or playing with in your View.
The variable in question (vm, $scope) is the context (this) of the instance being created by the Controller function.
$scope is Angular's "special" context; it has some behaviors already defined on it for you to use (e.g. $scope.$watch). $scopes also follow an inheritance chain, i.e. a $scope inherits the state and behaviors assigned to its parent $scope.
Take these two controllers:
angular.module("Module")
.controller("Controller", ["$scope", function($scope) {
$scope.tab = 0;
$scope.incrementTab = function() {
$scope.tab++;
};
}])
.controller("OtherController", ["$scope", function($scope) {
// nothing
}]);
And a view
<div ng-controller="Controller">
<p>{{ tab }}</p>
<button ng-click="incrementTab();">Increment</button>
<div ng-controller="OtherController">
<p>{{ tab }}</p>
<button ng-click="incrementTab();">Increment</button>
</div>
</div>
Example here
What you'll notice is that even though we didn't define $scope.tab in OtherController, it inherits it from Controller because Controller is it's parent in the DOM. In both places where tab is displayed, you should see "0". This may be the "hoisting" you're referring to, although that is an entirely different concept.
What's going to happen when you click on the first button? In both places we've exposed "tab", they will now display "1". Both will also update and increment when you press the second button.
Of course, I may very well not want my child tab to be the same tab value as the parent. If you change OtherController to this:
.controller("OtherController", ["$scope", function($scope) {
$scope.tab = 42;
}]);
You'll notice that this behavior has changed - the values for tab are no longer in sync.
But now it's confusing: I have two things called "tab" that aren't the same. Someone else may write some code later down the line using "tab" and break my code inadvertently.
We used to resolve this by using a namespace on the $scope, e.g. $scope.vm and assign everything to the namespace: $scope.vm.tab = 0;
<div ng-controller="OtherController">
<p>{{ vm.tab }}</p>
<button ng-click="vm.incrementTab();">Increment</button>
</div>
Another approach is to use the simplicity and brevity of this and take advantage of the controllerAs syntax.
.controller("OtherController", function() {
this.tab = 0;
});
<div ng-controller="OtherController as oc">
<p>{{ oc.tab }}</p>
</div>
This may be more comfortable for people who are used to using plain JS, and it's also easier to avoid conflicts with other Angular sources this way. You can always change the namespace on the fly. It's also a bit "lighter" on performance since you're not creating a new $scope instance, but I'm not sure there's much gain.
In order to achieve privacy, I would recommend encapsulating your data in a Service or Factory. Remember, Controllers aren't always singletons; there is a 1:1 relationship between a View and a Controller and you may instantiate the same controller more than once! Factories and Service objects are, however, singletons. They're really good at storing shared data.
Let all Controllers get a copy of the state from the singleton, and make sure all Controllers are modifying the singleton state using behaviors defined on the Service/Factory.
function modalController(dataItemsService) {
var vm = this;
vm.selectedItems = dataItemsService.SelectedItems(); // get a copy of my data
vm.updateItem = dataItemService.UpdateItem; // update the source
}
But wait, how do I know when another part of my app has changed my private data? How do I know when to get a new copy of SelectedItems? This is where $scope.$watch comes into play:
function modalController(dataItemsService, $scope) {
var vm = this;
vm.updateItem = dataItemService.UpdateItem; // update the source
// periodically check the selectedItems and get a fresh copy.
$scope.$watch(dataItemsService.SelectedItems, function(items) {
vm.items = items;
});
// thanks $scope!
}
If your data is not shared, or if your private data is representative of the View layer and not the Model layer, then it's totally OK to keep that in the controller.
function Controller() {
var buttonClicked = false;
this.click = function() {
buttonClicked = true; // User can not lie and say they didn't.
};
}
Lastly, DO NOT USE JQUERY IN YOUR CONTROLLER, as your reference did!
$("#existConfirmDialog").modal('hide');
This example might not be purely evil, but avoid accessing and modifying the DOM outside a Directive, you don't want to break other parts of your app by modifying the DOM underneath it.

AngularJS parent directive communicate with child directive

Consider two nested directives with isolate scopes:
<dctv1>
<dctv2></dctv2>
<dctv1>
If I want dctv2 to talk to dctv1 I have may options:
I may require the controller of dctv1 in the definition of dctv2 using the require:'^dctv1'
I may call an expression on the parent scope with the wrapper <dctv2 callParent="hello()"></dctv2> and scope:{callParent:'&'}
I can also use $scope.$emit in dctv2 but then all parent scopes will hear the message.
Now I want dctv1 to talk to dctv2.
The only way I may accomplish this is to use $scope.$broadcast, but then all children will hear.
By talk to here i mean call a function or similar. Don't want to set up watches clogging the digestloop.
How can I make dctv1 notify dctv2 in the best way, making them loose-coupled? I should just be able to remove dctv2 without errors.
Take a look at AngularJS NgModelController for some ideas.
Each <dctv2> directive would require <dvtv1> to have it's controller injected. You can then add objects or callbacks to properties of that controller, and remove them when <dctv2> is destroyed.
<dvtv1> would not talk directly to children, but would trigger callbacks bound to it's properties.
For example;
NgModelController has $parsers and $formatters that are an array of function callbacks. You push your own functions into the array to extend that controllers behavior.
When NgModelController performs input validation it's basically talking to other directives via these properties.
I would suggest using angular services. That way you can decouple your behavior into one or more services.
Take a look at this also : AngularJS : How to watch service variables?
One way is to make a Service/Factory that will communicate with the controllers that you want.
For example, here's a getter/setter Factory
.factory('factoryName', function () {
var something = "Hello";
return {
get: function () {
return something;
},
set: function (keyword) {
something = keyword;
return something ;
}
};
}])
And then in your controllers:
.controller('controllerOne', ['factoryName', function (factoryName) {
$scope.test = factoryName.get();
}]);
.controller('controllerTwo', ['factoryName', function (factoryName) {
$scope.test = factoryName.get();
$scope.clickThis = function (keyword) {
factoryName.set(keyword);
};
}]);
I suggest reading up on this : Can one controller call another?
You can manage it using an id for each child that have to be passed to the parent; the parent will broadcast back the event using that id: the child will do the action only if the id passed from the parent is the his own.
Bye

In AngularJS, how does $scope get passed to scope?

I'm a bit confused with the use of $scope in controllers and of scope in directives. Please verify if my understanding is correct (and also provide some alternative ways how to do this).
Let's say I have an html:
<div ng-controller="app1_Ctrl">
.
.
.
<input type="text" ng-model="value"/>
<input type="checkbox" />
<button ng-click="submit()"></button>
</div>
And my main.js
(function() {
angular.module('mainApp', ['app1']);
})();
And my app1 looks like this (based on official AngularJS documentation here)
(function() {
var app = angular.module('app1', []);
app.controller('app1_Ctrl', ["$scope", function($scope) {
.
.
.
}]);
app.directive('app1_Dir1', [function() {
function link(scope, element, attr) {
scope.$watch(attr.someAttrOfCheckBox, function() {
// some logic here
});
function submit() {
// some logic here
}
}
return link;
}]);
})();
How does $scope.value passed in scope in directive so that I can do some manipulations there? Will ng-click fire the function submit() in the directive link? Is it correct to use scope.$watch to listen for an action (ticked or unticked of course) in checkbox element?
Many thanks to those who can explain.
By default, directive scope is controller $scope; but it means the directive is directly dependent on your controller and you need a different controller for each instance of the directive you want to use. It is usually considered a best practice to isolate your directive scope and specifically define the variables you wish to pass it from your controller.
For this, you will need to add a scope statement to your directive :
scope {
label :'#',
context : '=',
function : '&'
}
and update your view :
<my-directive label="labelFromController" context="ctxtFromController" function="myFunction()" ></my-directive>
The symbols denote the kind of thing you wish to pass through : # is for one-way binding (as a string in your directive), = is for two-way binding of an object (which enables the directive to update something in your controller), and & is for passing a function.
There are a lot of additional options and subtleties that are best explained by the Angular doc https://docs.angularjs.org/guide/directive. There are also some nice tutorials out there (e.g. http://www.sitepoint.com/practical-guide-angularjs-directives/)
Your submit() function is not attached to anything, so you won't be able to call if from your viewer. You need to define it as scope.submit = function() ... in your link function if you wish to access it.
You can use $watch for this kind of thing, but there are usually other more elegant ways to achieve this by leveraging the fact that angular already "watches" the variables it is aware of and monitors any changes he can (this can be an issue when some external service changes data for exemple, because angular cannot listen to events it is not made aware of). Here, you can probably simply associate the ng-model directive to your input checkbox to store its true/fale (checked/unchecked) value, and the ng-change or ng-click directives to act on it. The optimal solution will mostly depend on the exact nature of your business logic.
Some additional thoughts :
The HTML insides of your directive should be packaged in an inline template field, or in a separate HTML file referenced by the templateUrl field in your directive.
In your HTML code above, your directive is not referenced anywhere. It should be an element, attribute or class (and your directive definition should reflect the way it can be called, with the restrict field). Maybe you have omitted the line containing the directive HTML, but as it stands, your directive doesn't do anything.
To my knowledge, you don't need to return link. Think of it as the "body" of your directive, where you define the variables and functions you will call in the HTML.
Your directive doesn't actually need HTML code and the above thoughts might be irrelevant if you are going in a different direction, but encapsulating some kind of view behaviour that you want to reuse is probably the most common use of directives.

How to structure angular factor/service that manipulates DOM

I'm building a growl like UI in angular. I'd like to expose it as a factory (or service) to make it available in my controllers. Calling growl.add will result in a change in the DOM, so it seems like I should have a directive take care of that, rather than doing direct DOM manipulation in the factory. Assuming that a factory-directive combo is the best option (and please correct me if that is not a good assumption), the question is:
How best to communicate between the factory and the directive?
Specifically, how best to send messages from the factory to the directive? Other questions have well covered sending information the other way, with onetime callback.
See below the working example. I suspect there is a better way though..
For reference, I have played with other options:
A) have the directive watch the service, e.g.
$scope.$watch(function(){
growl.someFunctionThatGetsNewData()},
function(newValue){
//update scope
})
But this means that someFunctionThatGetsNewData gets called in every digest cycle, which seem wasteful, since we know that the data only gets changed on growl.add
B) send an 'event', either via routescope, or via event bindings on the dom/window. Seem un-angular
Since neither of those options seem good, I'm using the one below, but it still feels hacky. The register function means that the directive and the factory are tightly coupled. But then again from usage perspective they are tightly bound - one is no good w/o the other.
It seem like the ideal solution would involve declaring a factory (or service) that includes the directive in its declaration (and perhaps functional scope) so that it exposes a single public interface. It seems icky to have two separate publicly declared components that entirely depend on each other, and which have tight coupling in the interfaces.
Working example - but there must be a better way..
vpModule.directive('vpGrowl',['$timeout', 'growl', function ($timeout, growl) {
return {
template: '<div>[[msg]]</div.',
link: function($scope, elm, attrs) {
growl.register(function(){
$scope.msg = growl.msg;
});
$scope.msg = growl.msg;
}
};
}]);
vpModule.factory('growl', ['$rootScope', '$sce', function($rootScope, $sce) {
var growl = {};
growl.msg = '';
var updateCallback = function(){};
growl.add = function(msg){
growl.msg = msg;
updateCallback();
};
growl.register = function(callback){
updateCallback = callback;
};
return growl;
}]);
I would have your growl service decide what to show, not the directive. So, the service handles any timers, state, etc. to decide when to hide/show messages. The service then exposes a collection of messages which the directive simply binds to.
The directive can inject the service and simply place it in scope, and then bind an ng-repeat to the service's collection. Yes, this does involve a watch, but you really don't need to worry about the performance of a single watch like this.
link: function(scope, elm, attrs) {
scope.growl = growl; // where 'growl' is the injected service
}
and then in the directive template:
<div ng-repeat="msg in growl.messages">
...
</div>
I would implement following logic:
Service growl defines some property growlProp on $rootScope & update it on each call of growl.add
Directive set watcher on $rootScope.growlProp
So directive knows nothing about service & service knows nothing about directve.
And additional overhead related to watcher is minimum.

Categories

Resources