How to use a custom directive inside a custom directive with require - angularjs

I am quite fairly new to AngularJS so I hope it's quite understandable why I'm having difficulty with this problem.
First I created a custom directive which uses another custom directive angular plugin http://vitalets.github.io/angular-xeditable/
'use strict';
require('../../../../vendor/angular-xeditable/xeditable.js');
angular.module('ft.core').directive('ftXeditableText', function() {
var result = {
restrict: 'E',
scope: {
item: "="
},
templateUrl: 'views/EditableText.html',
link: function (scope, element, attrs) {
}
};
return result;
});
And this is my template file EditableText.html
<span editable-text="{{item.Name}}">
{{item.Name}}
</span>
In my html code, it renders the resulting tag
<ft-xeditable-text item="ngModel[0]" class="ng-scope ng-isolate-scope">
<span editable-text="XEditable Text" class="ng-binding">
XEditable Text
</span>
</ft-xeditable-text>
I can't seem to make the plugin work. It renders in the browser but the plugin's feature doesn't execute. How should I approach this? Please help me. Thanks

Related

How do I specify the parent I want to transclude a directive to?

I have a lightbox directive that when it transcludes, it injects in the content. Technically, wherever I put the actual call to the directive. But essentially, I need it to transclude to the body so that the backdrop can cover the entire page viewport, and so that it centers to the viewport and not the container is transcluding to right now.
The directive is written this way:
(function() {
'use strict';
angular
.module('app.core')
.directive('lightboxDirective', lightboxDirective);
function lightboxDirective() {
return {
restrict: 'E',
transclude: true,
scope: {},
template: '<section class="md-lightbox" ng-transclude></section>',
};
}
})();
The lightbox is located in the page this way:
<div id="scala-media-media" class="page-layout">
<div class="center" flex>
<div class="header">
<img ng-src="{{vm.media.images[0].url}}" ng-click="showLightBox = true">
</div>
<div class="content">
... the page content here ...
</div>
</div>
</div>
<lightbox-directive class="angular-lightbox" ng-class="{ removed : !showLightBox }">
... the code for my lightbox content (which works fine by the way) ...
</lightbox-directive>
At first I thought I'd specify the parent as I would normally do in a $mdDialog (Angular Material) with parent: angular.element($document.body), but that didn't work out. parent is not a recognized callback I assume.
Notice in the image where it is injected. Right where I place it! What's the point of using a directive if I can just place the code there and will do the same thing without the directive? right?
Here is a screenshot of what is happening. Notice the backdrop and centering issue I am having on the left side, as opposed to what I desire in the right side with a dialog.
I am using this angular lightbox found in this CODEPEN
UPDATE
I moved the lightbox to its own template file, so as to not feel guilty for using the code directly on the page which makes the use of a directive redundant. And restructured my directive as it shows below. But the rendering on runtime is still a problem. The template injects where it is called. Now if only I could declare the body as the parent for it to append to! lol
New directive:
function lightboxDirective() {
return {
restrict: 'E',
templateUrl: function(elem,attrs) {
return attrs.templateUrl || 'app/main/pages/scala-media/views/lightbox-template.html'
}
};
}
Thanks in advance!
You can make the element reposition itself just before the closing body tag with
.directive("lightboxDirective", function() {
return {
restrict: 'E',
transclude: true,
scope: {},
template: '<section class="md-lightbox" ng-transclude></section>',
link: function(scope, elem) {
angular.element(document).find("body").append(elem);
}
};
});

Different scope when using ng-repeat and custom directive in one element

I have a directive like:
angular.module('myApp').directive('myDirective', function() {
return {
templateUrl: '/views/myView.html',
restrict: 'E',
link: function (scope, element, attrs) {
console.log(scope);
console.log(angular.element(element).scope());
}
};
});
the template html is:
<div>{{item.text}}</div>
and view html is:
<div ng-init="items=[{text:'hello'}, {text: 'world'}]">
<my-directive ng-repeat="item in items"></my-directive>
</div>
I find the scope and angular.element(element).scope() is not the same one, but I have to use angular.element(element).scope() way to get the scope of ngRepeat-item somewhere else.
Am I misunderstanding something?
Note:
The code could be reproduced only when including jQuery.
Here is related github issue.
The main reason is: angular1.2.x does not support jQuery2.0.x, and angular1.3.x will do.
So the solution here will be:
angular1.2.x + jQuery 1.x
angular1.3.x + jQuery 2.x
angularjs only

Is there an alternative to using a directive to create template code?

I had HTML that was duplicated on many screens so I created a directive like this:
app.directive('adminRetrieveButton', ['stateService', function (stateService) {
return {
restrict: 'AE',
template: "<button id='retrieveButton'\
ng-disabled='!home.forms.grid.$pristine'\
ng-click='ctrl.retrieve()' >Retrieve\
<span class='fa fa-fw mlr75'\
ng-class='{\"fa-spin fa-spinner\": stateService.action[Network.Retrieve], \"fa-download\": !stateService.action[Network.Retrieve] }' >\
</span>\
</button>",
link: function (scope, element, attrs) {
scope.stateService = stateService;
}
};
}]);
The directive and other similar ones has no functionality other than to create some template type of code. Now rather than having to code in 8 lines of HTML in each page I just have one line:
<admin-retrieve-button></admin-retrieve-button>
Is there another alternative to this that would make it even simpler without my needing to create a directive?
yes you dont need to create directive only for template you can simply use ng-include
Directives are a powerful tool for working with and modifying the DOM, If you dont manipulating with DOM you dont need directive
There are alternatives, but your method is the best. The way you have it is the correct Angular Way; if you have a standard control, make it a directive.
If you like, you can use the templateUrl option to include the HTML in a separate file instead of inline in the directive configuration:
app.directive('adminRetrieveButton', ['stateService', function (stateService) {
return {
restrict: 'AE',
templateUrl: "adminButton.html",
link: function (scope, element, attrs) {
scope.stateService = stateService;
}
};
}]);
...and in adminButton.html:
<button id="retrieveButton"
ng-disabled="!home.forms.grid.$pristine"
ng-click="ctrl.retrieve()" >Retrieve
<span class="fa fa-fw mlr75"
ng-class="{'fa-spin fa-spinner': stateService.action[Network.Retrieve],
'fa-download': !stateService.action[Network.Retrieve]}">
</span>
</button>
This directive can access its parent scope because it has not been set to isolate scope; if you included a "scope" option on the directive call you'd need to pass in the home and ctrl variables.
You can use ng-include to do this, but your view code will look less semantic and injecting stateService will be a bit more complicated.

Pass form to directive

I want to encapsulate my form fields in a directive so I can simply do this:
<div ng-form='myForm'>
<my-input name='Email' type='email' label='Email Address' placeholder="Enter email" ng-model='model.email' required='false'></my-input>
</div>
How do I access the myForm in my directive so I can do validation checks, e.g. myForm.Email.$valid?
To access the FormController in a directive:
require: '^form',
Then it will be available as the 4th argument to your link function:
link: function(scope, element, attrs, formCtrl) {
console.log(formCtrl);
}
fiddle
You may only need access to the NgModelController though:
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
console.log(ngModelCtrl);
}
fiddle
If you need access to both:
require: ['^form','ngModel'],
link: function(scope, element, attrs, ctrls) {
console.log(ctrls);
}
fiddle
Here a complete example (styled using Bootstrap 3.1)
It contains a form with several inputs (name, email, age, and country).
Name, email and age are directives. Country is a "regular" input.
For each input is displayed an help message when the user does not enter a correct value.
The form contains a save button which is disabled if the form contains at least one error.
<!-- index.html -->
<body ng-controller="AppCtrl">
<script>
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope) {
$scope.person = {};
});
</script>
<script src="inputName.js"></script>
<script src="InputNameCtrl.js"></script>
<!-- ... -->
<form name="myForm" class="form-horizontal" novalidate>
<div class="form-group">
<input-name ng-model='person.name' required></input-name>
</div>
<!-- ... -->
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<button class="btn btn-primary" ng-disabled="myForm.$invalid">
<span class="glyphicon glyphicon-cloud-upload"></span> Save
</button>
</div>
</div>
</form>
Person: <pre>{{person | json}}</pre>
Form $error: <pre>{{myForm.$error | json}}</pre>
<p>Is the form valid?: {{myForm.$valid}}</p>
<p>Is name valid?: {{myForm.name.$valid}}</p>
</body>
// inputName.js
app.directive('inputName', function() {
return {
restrict: 'E',
templateUrl: 'input-name.html',
replace: false,
controller: 'InputNameCtrl',
require: ['^form', 'ngModel'],
// See Isolating the Scope of a Directive http://docs.angularjs.org/guide/directive#isolating-the-scope-of-a-directive
scope: {},
link: function(scope, element, attrs, ctrls) {
scope.form = ctrls[0];
var ngModel = ctrls[1];
if (attrs.required !== undefined) {
// If attribute required exists
// ng-required takes a boolean
scope.required = true;
}
scope.$watch('name', function() {
ngModel.$setViewValue(scope.name);
});
}
};
});
// inputNameCtrl
app.controller('InputNameCtrl', ['$scope', function($scope) {
}]);
Edit 2: I'll leave my answer, as it might be helpful for other reasons, but the other answer from Mark Rajcok is what I originally wanted to do, but failed to get to work. Apparently the parent controller here would be form, not ngForm.
You can pass it in using an attribute on your directive, although that will get rather verbose.
Example
Here's a working, simplified jsFiddle.
Code
HTML:
<div ng-form="myForm">
<my-input form="myForm"></my-input>
</div>
Essential parts of the directive:
app.directive('myInput', function() {
return {
scope: {
form: '='
},
link: function(scope, element, attrs) {
console.log(scope.form);
}
};
});
What's happening
We've asked Angular to bind the scope value named in the form attribute to our isolated scope, by using an '='.
Doing it this way decouples the actual form from the input directive.
Note: I tried using require: "^ngForm", but the ngForm directive does not define a controller, and cannot be used in that manner (which is too bad).
All that being said, I think this is a very verbose and messy way to handle this. You might be better off adding a new directive to the form element, and use require to access that item. I'll see if I can put something together.
Edit: Using a parent directive
OK, here's the best I could figure out using a parent directive, I'll explain more in a second:
Working jsFiddle using parent directive
HTML:
<div ng-app="myApp">
<div ng-form="theForm">
<my-form form="theForm">
<my-input></my-input>
</my-form>
</div>
</div>
JS (partial):
app.directive('myForm', function() {
return {
restrict: 'E',
scope: {
form: '='
},
controller: ['$scope', function($scope) {
this.getForm = function() {
return $scope.form;
}
}]
}
});
app.directive('myInput', function() {
return {
require: '^myForm',
link: function(scope, element, attrs, myForm) {
console.log(myForm.getForm());
}
};
});
This stores the form in the parent directive scope (myForm), and allows child directives to access it by requiring the parent form (require: '^myForm'), and accessing the directive's controller in the linking function (myForm.getForm()).
Benefits:
You only need to identify the form in one place
You can use your parent controller to house common code
Negatives:
You need an extra node
You need to put the form name in twice
What I'd prefer
I was trying to get it to work using an attribute on the form element. If this worked, you'd only have to add the directive to the same element as ngForm.
However, I was getting some weird behavior with the scope, where the myFormName variable would be visible within $scope, but would be undefined when I tried to access it. That one has me confused.
Starting with AngularJS 1.5.0, there is much cleaner solution for this (as opposed to using the link function directly). If you want to access a form's FormController in your subcomponent's directive controller, you can simply slap the require attribute on the directive, like so:
return {
restrict : 'EA',
require : {
form : '^'
},
controller : MyDirectiveController,
controllerAs : 'vm',
bindToController : true,
...
};
Next, you'll be able to access it in your template or directive controller like you would any other scope variable, e.g.:
function MyDirectiveController() {
var vm = this;
console.log('Is the form valid? - %s', vm.form.$valid);
}
Note that for this to work, you also need to have the bindToController: true attribute set on your directive. See the documentation for $compile and this question for more information.
Relevant parts from the documentation:
require
Require another directive and inject its controller as the fourth argument to the linking function. The require property can be a string, an array or an object:
If the require property is an object and bindToController is truthy, then the required controllers are bound to the controller using the keys of the require property. If the name of the required controller is the same as the local name (the key), the name can be omitted. For example, {parentDir: '^parentDir'} is equivalent to {parentDir: '^'}.
Made your 'What I'd prefer' fiddle work!
For some reason you could see the "$scope.ngForm" string in a console.log, but logging it directly didn't work, resulting in undefined.
However, you can get it if you pass attributes to the controller function.
app.directive('myForm', function() {
return {
restrict: 'A',
controller: ['$scope','$element','$attrs', function($scope,$element,$attrs) {
this.getForm = function() {
return $scope[$attrs['ngForm']];
}
}]
}
});
http://jsfiddle.net/vZ6MD/20/

ng-repeat in combination with custom directive

I'm having an issue with using the ng-repeat directive in combination with my own custom directive.
HTML:
<div ng-app="myApp">
<x-template-field x-ng-repeat="field in ['title', 'body']" />
</div>
JS:
angular.module('myApp', [])
.directive('templateField', function () {
return {
restrict: 'E',
compile: function(element, attrs, transcludeFn) {
element.replaceWith('<input type="text" />');
}
};
});
See jSFiddle
The problem here is that nothing is replaced. What I'm trying to accomplish is an output of 2x input fields, with the 'x-template-field' tags completely replaced in the DOM. My suspicion is that since ng-repeat is modifying the DOM at the same time, this won't work.
According to this Stack Overflow question, the accepted answer seems to indicate this has actually worked in earlier versions of AngularJS (?).
Wouldn't element.html('...') work?
While element.html('...') actually injects the generated HTML into the target element, I do not want the HTML as a child element of the template tag, but rather replace it completely in the DOM.
Why don't I wrap my template tag with another tag that has the ng-repeat directive?
Basically, for the same reason as above, I don't want my generated HTML as a child element to the repeating tag. While it would probably work decently in my application, I would still feel like I've adapted my markup to fit Angular and not the other way around.
Why am I not using the 'template' property?
I haven't found any way to alter the HTML retrieved from the 'template' / 'templateUrl' properties. The HTML I want to inject is not static, it's dynamically generated from external data.
Am I too picky with my markup?
Probably. :-)
Any help is appreciated.
Your directive needs to run before ng-repeat by using a higher priority, so when ng-repeat clones the element it is able to pick your modifications.
The section "Reasons behind the compile/link separation" from the Directives user guide have an explanation on how ng-repeat works.
The current ng-repeat priority is 1000, so anything higher than this should do it.
So your code would be:
angular.module('myApp', [])
.directive('templateField', function () {
return {
restrict: 'E',
priority: 1001, <-- PRIORITY
compile: function(element, attrs, transcludeFn) {
element.replaceWith('<input type="text" />');
}
};
});
Put your ng-repeat in the template. You could modify attributes of element and accordingly in directive to determine if ng-repeat is needed, or what data to use inside the directive compiling
HTML(attribute):
<div ng-app="myApp" template-field></div>
JS:
angular.module('myApp', [])
.directive('templateField', function () {
return {
restrict: 'A',
template:'<input type="text" value="{{field}" ng-repeat="field in [\'title\',\'body\']" />'
};
});
DEMO: http://jsfiddle.net/GDfxd/3/
Also works as an element :
HTML(element):
<div ng-app="myApp" >
<template-field/>
</div>
JS
angular.module('myApp', [])
.directive('templateField', function () {
return {
restrict: 'E',
replace:true,
template:'<input type="text" value="{{field}}" ng-repeat="field in [\'title\',\'body\']" />'
};
});
DEMO: http://jsfiddle.net/GDfxd/3/

Resources