How to refactor directive and change functionality by checking parent scopes? - angularjs

I have a form with a ton of duplicate functionality in 2 different Controllers, there are slight differences and some major ones in both.
The form sits at the top of a products view controller, but also inside of the products modal controller.
Test plunker: http://plnkr.co/edit/EIW6xoBzQpD26Wwqwwap?p=preview
^ how would you change the string in the console.log and the color of the button based on parent scope?
At first I was going to create a new Controller just for the form, but also the HTML was being duplicated, so decided to put that into a Directive, and just add the Controller code there.
My question now is this: How would I determine which parent scope the form-directive is currently being viewed in? Because depending on the parent scope the functions/methods behave differently.
So far I've come up with this:
.directive('productForm', function() {
return {
templateUrl: "views/products/productForm.html",
restrict: "E",
controller: function($scope) {
console.log('controller for productForm');
console.log($scope);
console.log($scope.$parent);
/*
If parent scope is the page, then this...
If parent scope is the modal then this instead...
*/
}
}
});
However it's giving me back $parent id's that look like 002 or 00p. Not very easy to put in if / else statements based on that information.
Have you guys run into this issue before?

You can define 'saveThis' in your controller and pass it to directive using '&'
scope: {
user: '=',
saveThis : '&'
},
please see demo here http://plnkr.co/edit/sOY8XZtEXLORLmelWssS?p=preview
That gives you more flexibility, in future if you want to use saveThis in another controller you can define it inside controller instead adding additional if statement to directive.

You could add two way binding variables in the directive scope, this allows you to specify which Ctrl variable gets bound to which directive variable
<my-directive shared="scopeVariable">
this way you achieve two way binding of the scopeVariable with the shared directive variable
you can learn more here
I advice against this practice and suggest you to isolate common logics and behaviours in services or factories rather than in directives
This is an example of a directive that has isolated scope and shares the 'title' variable with the outer scope.
You could declare this directive this way:
now inside the directive you can discriminate the location where the directive is defined; just replace the title variable with a location variable and chose better names.
.directive('myPane', function() {
return {
restrict: 'E',
scope: {
title: '#'
},
link: function(scope, element, attrs, tabsCtrl) {
},
templateUrl: 'my-pane.html'
};
});

Related

Directive with it's own controller placed within ngAnimateSwap results in new controller being initialized on every 'swap'

I have created a directive (ParentDir) that has it's own controller and whose template binds to this controller. It communicates with another directive (Child1) that has it's own controller which 'requires' the first parent directive. Below is a simplified example:
Module.directive("ParentDir", function () {
return {
templateUrl: '../ParentTemplate',
restrict: 'AEC',
scope: {
},
controllerAs: 'parCtrl',
bindToController: true,
controller: ['$scope', function ($scope) {
parCtrl= this;
parCtrl.title = "PARENT 1 TITLE";
}]}
Module.directive("Child1", function () {
return {
templateUrl: '../Child1Template',
restrict: 'AEC',
require: '^^ParentDir',
scope: {},
controllerAs: 'ch1Ctrl',
bindToController: true,
link: function ($scope, element, attrs, parCtrl) {
$scope.parCtrl= parCtrl;
},
controller: ['$scope', function ($scope) {
ch1Ctrl= this;
ch1Ctrl.title = "CHILD 1 TITLE";
}]}
ParentDir html:
<child1> </child1>
Child1 html:
{{parCtrl.title}}
{{ch1Ctrl.title}}
Finally my ParentDirective is initialized in something like this:
<div ng-animate-swap="trigger" class="swapclass">
<parent-dir></parent-dir>
</div>
I need the entire parent directive's template to slide in certain cases. I also use the directive in other places where I don't need this and I can use it as is. In the cases where I do need the slide animation, I place it inside an ng-animate-swap as shown above. The problem is that every time the swap trigger changes, a new parCtrl is initialized causing everything to be reset!
How can I use animate swap with a directive that has isolate scope and it's own controller, without reinitializing the controller everytime a swap occurs?
Directives, as we know, are high-level markers that tell Angular's compiler to attach a specified behavior to that HTML element. When a directive is put on the DOM, Angular's $compile service matches directive names with their code, normalizes it and executes it.
However, ng-animate-swap removes its element from the DOM before appending the new one.
This means your directives are being recompiled for each swap, and new isolate scopes are created each time the animation happens.
The solution to this depends on the functionality of your app, how large your templates are, and how often you need to do the animation (or what the animation entails):
One solution is to create another directive outside of the swap animation that holds parCtrl.title and ch1Ctrl.title (or whatever other variables you have) and then is able to pass that information down to child scopes through prototypical inheritance:
<swap-dir>
<div ng-animate-swap="trigger" class="swapclass">
<parent-dir></parent-dir>
</div>
<swap-dir>
This could also be done with a controller, perhaps much more easily. What you choose to do would depend on where you're getting your scope variables from and how many different elements you have on the page.
However, ng-animate-swap creates its own scope, so while I believe this would work, the ever-fun JavaScript inheritance shenanigans could cause an issue here as well.
Another solution would be to skip ng-animate-swap altogether and just animate the template element with regular old CSS transitions, although this depends on what you're doing and how you want it to look.

What is scope inside directive does in AngularJS

I am learning angular and I am from jQuery background and facing problem to get hold on angular. so i often stumble to understand many things in angular code.
Just seen the code below and I do not understand what scope is doing in below directives?
But if I remove the scope from below directive then what will not work?
so please help me to understand the usage of scope and its importance with a example if possible. Thanks
<li my-directive price="item.price" ng-repeat="item in products">{{item.name}} — {{item.price}}</li>
myApp.directive('myDirective', function(){
return {
scope: { price: '=' },
require: 'ngModel',
link : function(scope){
console.log(scope.price)
},
controller: function(scope, element, attrs, ngModel){
console.log(ngModel.price);
console.log(scope.price);
}
}
});
In that sample you have scope: { price: '=' }, states that internal scope variable price of the directive is exactly binded to parent's scope. It has access to it and i value changes in parent scope, directive's value of price will change as well.
Remove this line from your directive scope: { ... } and then your directive will not create a new scope. But it will still work - meaning it won't create an error. There are use cases when you need or need not to have isolated scopes.
To figure out better how things work with angular and scopes - please check the following great resources:
angular vs jQuery -
"Thinking in AngularJS" if I have a jQuery background?
$scope itself in angular -
How does data binding work in AngularJS?
various types of scope variables in directives -
What is the difference between '#' and '=' in directive scope in AngularJS?
If you don't mention the scope configuration object your directive will use is parent's scope, but when you mention the scope object it creates its own isolated scope.
So if you don't mention the scope object you will have access to variables from your parent controller directly.
ref : http://www.undefinednull.com/2014/02/11/mastering-the-scope-of-a-directive-in-angularjs/

Common directive ng-click guidance needed

I have a directive which consists of a form text element and a continue button along with the associated controller etc. This directive is going to be used in about 5 different pages, but on each page it is used the continue button will do something different.
My question is where can/should I put the code for the continue button if it does different things for each page?
Since its a directive I cant simply pass a different function into ng-click depending on what page im on (ie, if i simply replicated the code on each page it is used I could simply change the function called on ng-click and have that function in each of the page controllers.
Hopefully Im not being too vague with my question and you can make sense of what im asking. If not just say so and ill try to explain in more detail.
I would really appreciate some guidance on this matter.
Thanks.
There are two ways that you can do it. If you are creating your directive as a true component you can use isolated scope with & binding that binds to an expression.
Assume your directive looks like
<div do-work on-click="save()"></div>
and the generated html
<div>
<input ...>
<button ng-click="doAction()"><button>
</div>
The directive scope will be defined
scope:{
onClick:'&'
}
In your directive controller or link function you need to implement the button doAction, which in turns evaluates the onClick action
scope.doAction=function() {
scope.onClick({//if params are required});
}
Now you have linked the parent through the direct onClick reference. One thing to remember here is that this creates a directive with isolated scope.
In case you do not want isolated scope created you need to use
scope.$eval(attr.onClick); // this evaluates the expression on the current scope.
Hope this helps.
Ideally you should not create directives which are not re-usable.
In your case, you may do it like following -
create an isolated scope in the directive
add a function to be called and pass the page/ page id as parameter
call functions in controller based on parameter
Directive
myApp.directive('someDirecive', function () {
return {
// restrict options are EACM. we want to use it like an attribute
restrict: 'A',
// template : <inline template string>
// templateUrl = path to directive template.
// templateUrl: '',
scope: {
onButtonClick : '&'
},
controller: function ($scope, $element, $attrs, $transclude) {
$scope.onButtonClick = function(pageId) {
if (pageId == 1) {
// do something
}
else if (pageId == 2) {
// do something
}
}
},
//link: function (scope, iElement, iAttrs) {
//}
};
});
HTML
<div some-directive on-button-click="DoSomething(1)" />

Do I need separate controllers from component-like directives in angularjs

I am writing custom element directives which are used to encapsulate HTML GUI or UI components. I am adding custom methods (that handles ng-click events, etc) in my link function such as:
app.directive('addresseseditor', function () {
return {
restrict: "E",
scope: {
addresses: "="
}, // isolated scope
templateUrl: "addresseseditor.html",
link: function(scope, element, attrs) {
scope.addAddress= function() {
scope.addresses.push({ "postCode": "1999" });
}
scope.removeAddress = function (index) {
scope.addresses.splice(index, 1);
}
}
}
});
Is the link function correct place to define the methods or is it better to create a separate controller object, use ng-controller and define methods there?
You can also define a controller per directive if you want. The main difference is that directives can share controllers (at the same level), controllers execute prior to compile, and controllers are injected (hence using the $). I think this is an accepted practice.
app.directive('addresseseditor', function () {
return {
restrict: "E",
scope: {
addresses: "="
}, // isolated scope
templateUrl: "addresseseditor.html",
controller: function($scope, $element, $attrs) {
$scope.addAddress= function() {
$scope.addresses.push({ "postCode": "1999" });
}
$scope.removeAddress = function (index) {
$scope.addresses.splice(index, 1);
}
}
}
});
You can have both link and controller... but you want to do any DOM stuff in the link because you know you're compiled.
This method also remains de-coupled since it's still part of your directive.
If you are permanently coupling this with a controller & view, then I would say it doesn't really matter where you put it.
However, if you one day want to decouple the directive so you can reuse it, think of what functionality needs to be included.
The Angular guide on directives reads:
The link function is responsible for registering DOM listeners as well
as updating the DOM. (...) This is where most of the directive logic
will be put.
I would follow the last part of that statement only if I had to write a directive that heavily manipulates the DOM. When all my directive does is render a template with some functionality, I use the link function to perform whatever basic DOM manipulation I need and the controller function to encapsulate the directive's logic. That way I keep things clearly separate (DOM manipulation from scope manipulation) and it seems consistent with the idea of "view-controller".
FWIW, I've implemented my first open source directive with those things in mind and the source code can be found here. Hopefully it might help you somehow.
If you want your elements functionality 'instance' specific place it in the link function, if you want to create an API across directives on an element create a controller function for it like master Rowny suggests.

Call controller method from directive without defining it on the directive element - AngularJS

I'm sure there's a simple answer to this that i've just missed.
http://jsfiddle.net/jonathanwest/pDRxw/3/
Essentially, my directive will contain controls that will always call the same method in a controller which is external to the directive itself. As you can see from the above fiddle, I can make this work by defining the attribute with the method on the control directive, but as that method will always be called from the same button within the directive, I don't want to have to define the method to call. Instead the directive should know to call the controller edit method when that button is pressed. Therefore, the definition of the control would be:
<control title="Custom Title" />
How can I achieve this?
Actually I think doing that straightway using $parent is not a recommended way how directives should be defined. Because actually there is no visible dependency on what functions could be called from parent controller, making them little bit harder to re-use.
I do not know actual use case why you need this, but I assume that you use control several times and you do not want to copy-paste bunch of attributes that defines some common behavior.
In this case I would recommend little bit other approach: add some directive-container that will define that behavior, and control will require this directive as dependency:
myApp.directive('controlBehavior', function() {
return {
restrict: 'E',
scope: {
modifyfunc: '&'
},
controller: function($scope, $timeout) {
this.modifyfunc = $scope.modifyfunc;
}
};
});
myApp.directive('control', function() {
return {
restrict: 'E',
require: '^controlBehavior',
replace: true,
scope: {
title: "#"
},
template : '<div>{{title}}<button ng-click="edit()">Edit</button></div>',
link: function(scope, element, attr, behavior) {
scope.edit = behavior.modifyfunc;
}
}
});
Here is a fiddle to demonstrate this approach: http://jsfiddle.net/7EvpZ/4/
You can access the parent scope by using $parent property of the current scope.
http://jsfiddle.net/XEt7D/

Resources