I am looking for a way to extend or wrap a third-party directives html with Angular 1.5.
Given a directive
<lib-input></lib-input>
I want to create a directive <my-lib-input> which renders the following HTML:
<div>
<my-directive></my-directive>
<lib-input ng-if="vodoo()"></lib-input>
</div>
Which is supposed to be used in the same way as the original directive.
Example
To use my directive in the same way as the original, i need to move all attributes to a specific node of my template:
<my-lib-input ng-model="model" ng-change="ctrl.onChange()"></my-lib-input>
should generate:
<div>
<my-directive></my-directive>
<lib-input ng-if="vodoo()" ng-model="model" ng-change="ctrl.onChange()"></lib-input>
</div>
However angular applies all the attributes to the root-node (here: the div) by default.
Question
How do I apply all parameters/ attributes which are passed to my directive to a specific node of the template?
I would like to prevent hardcoding a list of available parameters in my directive like:
restrict: 'E',
scope : {
ngModel: '=',
ngChange: '&',
...
}
You can have chaining of scope parameters like
Working JSFiddle here
var myApp = angular.module('myApp',[]);
myApp.controller('myCtrl', function($scope) {
$scope.input = 'LibInput';
$scope.changeInput2 = function(i2) {
$scope.myInputs.setInput2(i2);
}
//this is releaving module which have getters and setter and variables can be hidden from outside scope.
var getInputData = function() {
var input1 = 'Input1';
var input2 = 'Input2';
return {
getInput1 : function() {
return input1;
},
getInput2 : function() {
return input2;
},
setInput1 : function(i1) {
input1 = i1;
},
setInput2 : function(i2) {
input2 = i2;
}
}
}
$scope.myInputs = getInputData();
});
myApp.directive('libInput', function() {
return {
restrict : 'E',
scope : {
input : '='
},
template : '<div>{{input}}</div>'
}
});
myApp.directive('myLibInput', function() {
return {
restrict : 'E',
scope : {
input : '=',
myDirInput : '='
},
template : '<my-dir other-input="myDirInput"></my-dir>\
<lib-input input="input"><lib-input>'
}
});
myApp.directive('myDir', function() {
return {
restrict : 'E',
scope : {
otherInput : '='
},
template : '<div>{{otherInput.getInput1()}}</div>\
<div>{{otherInput.getInput2()}}</div>'
}
});
Related
I currently have an issue when I call ui-tinymce directive in a custom directive. The custom directive is used to load dynamically links from backend for tinymce advlink plugin (+ load tinymce options object associated with a key passed as an attribute to the directive).
Here is my controller :
module.controller('Ctrl', function ($scope) {
$scope.test = {
val: "gfsgfdgh"
};
});
Here is how I call the directive in HTML:
<tinymce-custom type="minimal" ng-model="test.val"></tinymce-custom>`
And here is my directive :
module.directive('tinymceCustom', function($location, TinyService, Module, GenerateurPage) {
return {
restrict: 'E',
replace: true,
require:"ngModel",
scope: {
ngModel: '='
},
link: function(scope, element, attrs, ngModel){
scope.loaded = {
modules: false,
pages: false,
tinymce: false
};
scope.tinyOptions = {};
var link_list = [];
var modules = [];
var pages = [];
Module.findByOrganisme({}, function (data) {
data.forEach(function(module) {
modules.push({title: module.libelle, value: "/modules/"+module.id});
});
link_list.push({title: "Modules", menu: modules});
scope.loaded.modules = true;
initTiny();
});
GenerateurPage.findByOrganisme({}, function(data) {
data.forEach(function(page) {
pages.push({title: page.titre, value: "/#/generateurPage/afficherPage?id=/"+page.id});
});
link_list.push({title: "Pages", menu: pages});
scope.loaded.pages = true;
initTiny();
});
function initTiny() {
if (!scope.loaded.modules || !scope.loaded.pages) {
return false;
}
scope.tinyOptions = TinyService.options(attrs.type);
console.log(scope);
scope.tinyOptions.link_list = link_list;
scope.loaded.tinymce = true;
}
},
template: '<div ng-if="loaded.tinymce"><textarea ui-tinymce="tinyOptions" ng-model="ngModel"></textarea></div>'
};
});
The problem is that the model passed to ui-tinymce directive is not updated when changing the text with the editor, and the text in the editor is not updated when the model from the controller is changed... BUT, the initial ngModel value is passed to ui-tinymce directive, so I think that is the data binding that is broken. Tried to watch it with $watch but nothing happens.
I can't figure how to fix it so I'm now looking for some help...
Thx
Finaly fixed it changing the approach :
<textarea tinymce-custom="minimal" ng-model="myVar"></textarea >
The final directive :
module.directive('tinymceCustom', function($location, $compile, $q, TinyService, Module, GenerateurPage) {
return {
restrict: 'A',
priority:999,
terminal:true, // prevent lower priority directives to compile after it
scope: true,
require: ['?ngModel'],
link: function(scope, el, attrs) {
// default is basic template
var type = attrs.tinymceCustom ? attrs.tinymceCustom : 'basic';
function loadTinyOptions(name) {
var loaded = {
modules: false,
pages: false,
tinymce: false
};
var link_list = [];
var deferred = $q.defer();
var initTiny = function() {
if (!loaded.modules || !loaded.pages) {
return false;
}
var tinyOptions = TinyService.options(name);
tinyOptions.link_list = link_list;
deferred.resolve(tinyOptions);
};
Module.findByOrganisme({}, function (data) {
var modules = [];
data.forEach(function(module) {
modules.push({title: module.libelle, value: "/modules/"+module.id});
});
link_list.push({title: "Modules", menu: modules});
loaded.modules = true;
initTiny();
});
GenerateurPage.findByOrganisme({}, function(data) {
var pages = [];
data.forEach(function(page) {
pages.push({title: page.titre, value: "/#/generateurPage/afficherPage?id=/"+page.id});
});
link_list.push({title: "Pages", menu: pages});
loaded.pages = true;
initTiny();
});
return deferred.promise;
}
loadTinyOptions(type).then(function(data) {
scope._tinyOptions = data;
el.removeAttr('tinymce-custom'); // necessary to avoid infinite compile loop
el.attr('ui-tinymce', '{{_tinyOptions}}');
$compile(el)(scope);
});
}
};
Hope this can help.
I create the simple directive:
angular.module('app').directive('field', function () {
return {
restrict: 'E',
template: '<div ng-click="clickElement()"><input id="{{inputId}}"></div>',
scope: {
inputId: '#'
},
controller: function ($scope) {
if (!$scope.inputId) {
$scope.inputId = 'abc';
}
function logId() {
console.log($scope.inputId); // 'abc'
console.log($scope); //$scope.inputId is undefined here (!!!)
}
logId();
$scope.clickElement = function() {
//$scope.inputId is undefined here
}
}
}
});
Then I use it without inputId field like this:
<field></field>
When I use directive my $scope.inputId is undefined but not 'abc' as I want.
Why? What can I do to get 'abc' if inputId not specified in directive usage?
P.S. The same situation when the code in postLink function.
EDIT:
Plank: http://plnkr.co/edit/0mpcbrdUdafCMzATmCYZ?p=preview
Ugly workaround (to show how it must work): http://plnkr.co/edit/G6gxQhgrteJBeNmR7yTX?p=preview
You can set a default attribute in compile and avoid assigning to the scope in the controller.
compile: function(element, attrs) {
if (!attrs.inputId) { attrs.inputId = 'abc'; }
},
And remove the code setting a default value.
After looking at your plank I would suggest you not to use the direct attribute inputId inside $scope. Instead use properties with '.' object notation as follows:
angular.module('app', []).directive('field', function () {
return {
restrict: 'E',
template: '<div ng-click="clickElement()"><input id="{{inputId || formObj.inputId}}"></div>',
scope: {
inputId: '#'
},
controller: function ($scope) {
$scope.formObj = {};
if (!$scope.formObj.inputId) {
$scope.formObj.inputId = 'abc';
}
function logId() {
console.log($scope.formObj.inputId); // 'abc'
console.log($scope); //$scope.inputId is undefined here (!!!)
}
logId();
$scope.clickElement = function(){
console.log($scope.formObj.inputId);
alert($scope.formObj.inputId);
//$scope.inputId is undefined here but must be 'abc'
}
}
}
}).controller("ctrl", function(){});
I am trying to write a simple custom directive in Angular that turns a tag into a toggle button (similar to a checkbox). The code I have written so far updates the internal variable (isolated scope) but the two way binding doesn't seem to work. When I click the button, the button toggles (the css class is appearing and disappearing) but myVariable is not updating.
Any help much appreciated!
Usage
<button toggle-button="myVariable">My Button</button>
Directive code
( function() {
var directive = function () {
return {
restrict: 'A',
scope: {
toggleButton: '=checked'
},
link: function( $scope, element, attrs ) {
$scope.$watch('checked', function(newVal, oldVal) {
newVal ? element.addClass ('on') : element.removeClass('on');
});
element.bind('click', function() {
$scope.checked = !$scope.checked;
$scope.$apply();
});
}
};
};
angular.module('myApp')
.directive('toggleButton', directive );
}());
just replace
scope: {
toggleButton: '=checked'
}
to
scope: {
checked: '=toggleButton'
}
Your directive scope is looking for an attribute that doesn't exist.
Try changing:
scope: {
toggleButton: '=checked'
},
To
scope: {
toggleButton: '='
},
The difference is that =checked would look for the attribute checked whereas = will use the same attribute as the property name in the scope object
Will also need to change the $watch but you could get rid of it and use ng-class
As charlietfl said, you don't need that checked variable. You are making changes to it instead of the external variable.
Here is a fixed version:
angular.module('components', [])
.directive('toggleButton', function () {
return {
restrict: 'A',
scope:{
toggleButton:'='
},
link: function($scope, $element, $attrs) {
$scope.$watch('toggleButton', function(newVal) {
newVal ? $element.addClass ('on') : $element.removeClass('on');
});
$element.bind('click', function() {
$scope.toggleButton = !$scope.toggleButton;
$scope.$apply();
});
}
}
})
angular.module('HelloApp', ['components'])
http://jsfiddle.net/b3b3qkug/1/
I have a list of directives (normally form fields and custom form controls). Now I will get the list of directive names from backend to use to build a form.
It basically creates a dynamic form where I don't know what all form fields are in the form (it depends on the JSON config file I get from the backend).
Sample JSON:
field1 : {
type: 'text',
directive : 'directive1'
},
field2: {
type : 'dropdown',
directive : 'dropdown-directive'
}
Can I do something similar in AngularJS, and if possible, how?
Use the $compile service against the scope. This will allow you to compile angular code which can be appended to a container.
See jsfiddle: http://jsfiddle.net/p8jjZ/1/
HTML:
<div ng-app="myApp" ng-controller="MainController">
<div custom-elements="myData.elements"></div>
<p>{{user}}</p>
</div>
JavaScript:
var mod = angular.module("myApp", []);
mod.controller("MainController", function ($scope) {
$scope.myData = {};
$scope.myData.elements = {
field1 :{ type: 'text', directive : 'directive1' },
field2: { type : 'dropdown', directive : 'dropdown-directive' }
};
});
mod.directive("customElements", function ($compile) {
return {
restrict: "A",
scope: {
customElements: "="
},
link: function (scope, element, attrs) {
var prop,
elems = scope.customElements,
currElem,
compiled;
for (prop in elems) {
currElem = elems[prop];
console.log("Working on " + prop);
//compile input against parent scope. Assuming directives are attributes, but adapt to your scenario:
compiled = $compile('<div ' + currElem.directive + '></div>')(scope.$parent);
//append this to customElements
element.append(compiled);
}
}
}
});
mod.directive("directive1", function () {
return {
restrict: "A",
template: '<div>Whoa! I am directive 1<br><input type="text" ng-model="user.name"></div>'
}
});
mod.directive("dropdownDirective", function () {
return {
restrict: "A",
template: '<div>I am another directive<br><select ng-model="user.color"><option value="blue">blue</option><option value="green">Green</option></div>'
}
});
The customElement directive just creates the directive as if it were an attribute on an element. This is a very simple example, but should get you started on what you are looking to do where you can update the logic that builds the elements/directive accordingly.
I got this directive in angularJS
productApp.directive('notification', function($timeout) {
return {
restrict : 'E',
replace : true,
scope : {
type: "#",
message: "#"
},
template : '<alert class="alert alert-type">message</alert>',
link : function(scope, element, attrs) {
$timeout(function() {
element.hide();
}, 3000);
}
}
});
So i can call it from the view like this:
<notification type="alert.type" message="alert.msg"></notification>
In the controller i got the alert object defined:
$scope.alert = { type : 'success', msg : 'This is a test'};
How am i able to pass the type dynamically? tried that and it didn't work. If i pass alert-success to the directive, it works, but i want it to be dynamic.
Am i able to pass it dynamically by doing that?
Thanks
Try to change directive to this one:
productApp.directive('notification', function($timeout) {
return {
restrict : 'E',
replace : true,
scope : {
type: "=",
message: "="
},
template : '<alert class="alert alert-{{type}}">{{message}}</alert>',
link : function(scope, element, attrs) {
$timeout(function() {
// element.hide();
}, 3000);
}
}
});
Since you have isolated scope use = to bind parent scope property
Demo Fiddle
In your link function, you can do something like this
attrs.type and attrs.msg to retrieve the value you pass to your directive.