I have a problem using angular 1.6.5 and tried everything but without any noticeable progress...
I made a directive that implement the HTML SELECT tag, the component seems to work until I try to get the entire object in option value attribute instead of a single value or ID.
I can output the text of the option tag both as a property of the object or trough a function the return some string.
Here is a complete fiddle with the component and the problem.
There are 3 examples:
The 1st one seems to work correctly, it print the correct text and return the correct value
The 2nd: do not work, I'd like to set to the model the entire OBJECT selected and not a property
The 3rd: do not work, I'd like to set to the model the entire OBJECT selected and not a property but it print correctly the text from a function in parent controller.
How can I change my component that it can return both a property (like ID) both the entire object (JSON format is good)?
angular.module("myApp", ['customDrop']).controller("TestController", ['$scope', function($scope) {
var ITEM_SELECTED = {
ID: 3,
VALUE: "VALUE3"
};
$scope.LIST = [{
ID: 1,
VALUE: "VALUE1"
},
{
ID: 2,
VALUE: "VALUE2"
},
ITEM_SELECTED,
];
$scope.OBJ = {
LOTTO1: ITEM_SELECTED,
LOTTO2: ITEM_SELECTED,
LOTTO3: ITEM_SELECTED
};
$scope.getCompleteValue = function(obj) {
return obj.ID + " - " + obj.VALUE;
}
}]);
angular.module('customDrop', []).directive('customDrop', function() {
return {
restrict: 'E',
scope: {
dropid: '#',
dropvalue: '&',
list: '=',
ngModel: '='
},
require: 'ngModel',
template: '<select class="drop" ng-model="ngModel">' +
'<option ng-repeat="val in list" value="{{getId(val)}}">{{getValue(val)}}</option>' +
'</select>',
controller: ['$scope', '$parse', '$timeout',
function($scope, $parse, $timeout) {
$scope.getId = function(obj) {
return obj[$scope.dropid];
}
// Can print text option as proerty of through function in parent scope.
$scope.getValue = function(obj) {
return !angular.isFunction($scope.dropvalue(obj)) ?
$scope.dropvalue(obj) :
$parse($scope.dropvalue(obj))(obj);
}
}
]
}
});
.drop {
width: 400px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.min.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<div ng-app="myApp" ng-controller="TestController">
<strong>Simple Text Property (drop id intended to return the ID): (working)</strong><br>
<custom-drop dropvalue="VALUE" dropid="ID" list="LIST" ng-model="OBJ.LOTTO1"></custom-drop><br> Selected Value: {{OBJ.LOTTO1}}
<br><br><br>
<!-- using property as dropValue -->
<strong>Simple Text Property (drop id intended to return an object): (not working)</strong><br>
<custom-drop dropvalue="VALUE" dropid="" list="LIST" ng-model="OBJ.LOTTO2"></custom-drop><br> Selected Value: {{OBJ.LOTTO2}}
<br><br><br>
<!-- using function as dropValue -->
<strong>Function Text Property: (not working)</strong><br>
<custom-drop dropvalue="getCompleteValue" dropid="" list="LIST" ng-model="OBJ.LOTTO3"></custom-drop><br> Selected Value: {{OBJ.LOTTO3}}
</div>
To set to the model the entire OBJECT selected you have to modify your getId() method to return the object in case $scope.dropid was not passed through the bindings (since this method is used to generate the value of the option). Also I recommend using ngOptions to generate the list of option elements. See the snippet below:
angular.module("myApp", ['customDrop']).controller("TestController", ['$scope', function ($scope) {
var ITEM_SELECTED = {
ID: 3,
VALUE: "VALUE3"
};
$scope.LIST = [{
ID: 1,
VALUE: "VALUE1"
},
{
ID: 2,
VALUE: "VALUE2"
},
ITEM_SELECTED,
];
$scope.OBJ = {
LOTTO1: ITEM_SELECTED.ID,
LOTTO2: ITEM_SELECTED,
LOTTO3: ITEM_SELECTED
};
$scope.getCompleteValue = function (obj) {
return obj.ID + " - " + obj.VALUE;
}
}]);
angular.module('customDrop', []).directive('customDrop', function () {
return {
restrict: 'E',
scope: {
dropid: '#',
dropvalue: '&',
list: '<',
ngModel: '='
},
require: 'ngModel',
template: '<select class="drop" ng-model="ngModel" ng-options="getModelValue(val) as getOptionText(val) for val in list"></select>',
controller: ['$scope', '$parse',
function ($scope, $parse) {
$scope.getModelValue = function (obj) {
return !!$scope.dropid ? obj[$scope.dropid] : obj;
};
$scope.getOptionText = function (obj) {
return !angular.isFunction($scope.dropvalue(obj)) ?
$scope.dropvalue(obj) :
$parse($scope.dropvalue(obj))(obj);
}
}
]
}
});
.drop {
width: 400px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.min.js"></script>
<div ng-app="myApp" ng-controller="TestController">
<strong>Simple Text Property (drop id intended to return the ID): </strong><br>
<custom-drop dropvalue="VALUE" dropid="ID" list="LIST" ng-model="OBJ.LOTTO1"></custom-drop><br> Selected Value: {{OBJ.LOTTO1}}
<br><br><br>
<!-- using property as dropValue -->
<strong>Simple Text Property (drop id intended to return an object): </strong><br>
<custom-drop dropvalue="VALUE" dropid="" list="LIST" ng-model="OBJ.LOTTO2"></custom-drop><br> Selected Value: {{OBJ.LOTTO2}}
<br><br><br>
<!-- using function as dropValue -->
<strong>Function Text Property: </strong><br>
<custom-drop dropvalue="getCompleteValue" dropid="" list="LIST" ng-model="OBJ.LOTTO3"></custom-drop><br> Selected Value: {{OBJ.LOTTO3}}
</div>
Related
Is it possible to pass a parameter to a directive and to set that value as the directive scope?
Example:
angular
.module('app', [])
.controller('CTRL', function($scope) {
$scope.some_value = {
instance1: {
key1: 'value11',
key2: 'value12'
},
instance2: {
key1: 'value21',
key2: 'value22'
},
};
})
.directive('uiClock', function() {
return {
restrict: 'E',
scope: {},
template: template,
link: function(scope, element, attr) {
// scope should now contain either (first directive)
// {
// key1: 'value11',
// key2: 'value12'
// }
// or (second directive)
// {
// key1: 'value21',
// key2: 'value22'
// }
console.log(scope);
}
};
});
<div ng-controller="Ctrl">
<ui-clock ng-bind="some_value.instance1"></ui-clock>
<ui-clock ng-bind="some_value.instance2"></ui-clock>
</div>
The reason I want to do this is I have multiple instances of same directive and each should modify the value passed as parameter from the parent scope.
Any thoughts?
You should use the two-way data binding.
In your directive, you can specify an isolate scope, and use the = syntax, which is pretty useful.
Controller
(function(){
function Controller($scope) {
$scope.some_value = {
instance1: {
key1: 'value11',
key2: 'value12'
},
instance2: {
key1: 'value21',
key2: 'value22'
},
};
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
Directive
(function(){
function directive($compile) {
return {
restrict: 'E',
scope: {
data: '='
},
templateUrl: 'template.html',
link: function(scope, element, attr) {
var elm = angular.element(element);
//For all key in scope.data
Object.keys(scope.data).forEach(function(key){
//Create a new property for our isolate scope
scope[key] = scope.data[key];
//Add attr to our element
elm.attr(key, scope[key]);
});
//Remove our data attribute
elm.removeAttr('data');
//Then we can access to scope.key1 & scope.key2
console.log(scope.key1);
console.log(scope.key2);
}
};
}
angular
.module('app')
.directive('directive', directive);
})();
Template
<div>Key 1 : {{key1}}</div>
<div>Key 2 : {{key2}}</div>
Then you can call your directive, by passing specific data to our isolate scope. If you want, you can remove data attribute for the parent element and replace it by the value of your object.
HTML
<body ng-app='app' ng-controller="ctrl">
<directive data='some_value.instance1'></directive>
<directive data='some_value.instance2'></directive>
</body>
If you check your directive element, the data attribute will be removed and replace by key1 = value... etc ...
You can see the Working Plunker
What is the most Angular recommended way to use a dynamic tag name in a template?
I have a drop-down containing h1-h6 tags. A user can choose any of these and the content will change to being wrapped by the chosen header tag (which is stored on the $scope). The content is bound to the model i.e. within {{ }}.
To persist the binding I can change the markup and use $compile. However, this does not work because it gets appended (obviously) before Angular replaces the {{ }} with model values. It's h3 on page load.
Example:
<div id="root">
<h3 id="elementToReplace">{{ modelData }}</h3>
</div>
When re-compiling I have tried using a string as follows:
<{{ tag }} id="elementToReplace">{{ modelData }}</{{ tag }}>
Any ideas?
Demo Plunker Here
Define a scope variable named 'tag' and bind it to both your select list and custom directive.
HTML:
<select ng-model="tag" ng-init="tag='H1'">
<option ng-value="H1">H1</option>
<option ng-value="H2">H2</option>
<option ng-value="H3">H3</option>
<option ng-value="H4">H4</option>
<option ng-value="H5">H5</option>
</select>
<tag tag-name="tag">Hey There</tag>
Next, pass the tag scope model into your directive using two-way model binding:
var app = angular.module('app',[]);
app.directive('tag', function($interpolate) {
return {
restrict: 'E',
scope: {
tagName: '='
},
link: function($scope, $element, $attr) {
var content = $element.html();
$scope.$watch('tagName', function(newVal) {
$element.contents().remove();
var tag = $interpolate('<{{tagName}}>{{content}}</{{tagName}}>')
({tagName: $scope.tagName, content: content});
var e = angular.element(tag);
$element.append(e);
});
}
}
});
Notice that in the custom directive, we are using the $interpolate service to generate the HTML element based on the Tag that was selected in the select list. A $watch function is used to watch for changes to the tag model, and when it changes, the new element is appended to the DOM.
Here is one I knocked up, even though you said you didn't want it ;)
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script src="../lib/jquery.js"></script>
<script src="../lib/angular.js"></script>
<script>
var app = angular.module('app', []);
app.controller('ctrl', ['$scope', function ($scope) {
$scope.modelData = "<h1>Model Data</h1>" +
"<p id='replace'>This is the text inside the changing tags!</p>";
$scope.tags = ["h1", "h2", "h3", "h4", "p"];
$scope.selectedTag = "p";
}]);
app.directive("tagSelector", function(){
return {
resrict: 'A',
scope: {
modelData: '#',
selectedTag: '#'
},
link: function(scope, el, attrs){
scope.$watch("selectedTag", updateText);
el.prepend(scope.modelData);
function updateText(){
var tagStart = "<" + scope.selectedTag + " id='replace'>";
var tagEnd = "</" + scope.selectedTag + ">";
$("#replace").replaceWith(tagStart + $("#replace").html() + tagEnd);
}
}
}
});
</script>
</head>
<body ng-app="app">
<div ng-controller="ctrl">
<select ng-model="selectedTag" ng-options="tag for tag in tags"></select>
<div tag-selector selected-tag="{{selectedTag}}" model-data="{{modelData}}"></div>
</div>
</body>
</html>
More kosher version. Work good with ng-repeat and nested directives.
Working example here.
angular.module('myApp', [])
.directive('tag', function($interpolate, $compile) {
return {
priority: 500,
restrict: 'AE',
terminal: true,
scope: {
tagName: '='
},
link: function($scope, $element) {
$scope.$on('$destroy', function(){
$scope.$destroy();
$element.empty();
});
$scope.$parent.$watch($scope.tagName, function(value) {
$compile($element.contents())($scope.$parent, function(compiled) {
$element.contents().detach();
var tagName = value || 'div';
var root = angular.element(
$element[0].outerHTML
.replace(/^<\w+/, '<' + tagName)
.replace(/\w+>$/, tagName + '>'));
root.append(compiled);
$element.replaceWith(root);
$element = root;
});
});
}
}
})
.controller('MyCtrl', function($scope) {
$scope.items = [{
name: 'One',
tagName: 'a'
}, {
name: 'Two',
tagName: 'span'
}, {
name: 'Three',
}, {
name: 'Four',
}];
});
Usages:
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<tag class="item" tag-name="'item.tagName'" ng-repeat="item in items">
{{item.name}}
</tag>
</div>
</div>
I am trying to update testVar1 the ng-model attr for the input. The value succesfully gets
$scope.testVar1 = menuElements[$scope.element.id].value;
But when i change the value of
menuElements[$scope.element.id].value;
I want testVar1 to update along with its input view
Is this possible? if so what am i doing wrong? I made a function below to try and hard set the code to val = 2 but it was not succesful it seems that the scope variables only update when you build the page(at least the way ive written it)
HTML:
<div class="well">
<label for="{{element.id}}">{{element.info}}:</label>
<input class="ui-slider" type="range" ng-model="testVar1" ng-change="changeValue(element.id)" name="{{element.id}}" min="{{element.min}}" max="{{element.max}}" id="{{element.id}}"/>
<button ng-click="setTestValue()">Test</button>
</div>
Directive and controller
cordovaAngular.directive('myCustomer', function () {
return {
restrict: 'A',
scope: {
element: '=',
elementArray: '='
},
templateUrl: elementURL,
controller: function ($scope) {
var test = JSON.stringify($scope.elementArray);
$scope.selectedOption = "Success"
$scope.testVar1 = menuElements[$scope.element.id].value;
console.log($scope.testVar1);
console.log($scope.element.id);
$scope.changeOption = function (selectedItem) {
$scope.selectedOption = selectedItem;
// alert(1);
}
$scope.changeValue = function (id) {
menuElements[id].onChange();
}
$scope.setTestValue = function () {
menuElements[$scope.element.id].value = 2;
$scope.testVar1.
console.log($scope.testVar1);
}
}
};
});
I think you can use $watch
Assign menuElements to a $scopevariable and add a $watch listener to it.
$scope.$watch('menuElements', function(newVal, oldVal){
// When menuElementes change, update testVar1 here
$scope.testVar1 = menuElements[$scope.element.id].value;
}, true);
The AngularJS docs for $watch
You need menuElements to be part of the scope in order to be able to watch changes in it. Since your directive has isolated scope, it should be in the scope of your directive. Here is an example of doing it:
HTML
<body ng-controller="ctrl" id="ctrl">
<ul>
<li ng-repeat="element in data.elementArray">{{element.id}} - {{data.menuElements[element.id].value}}</li>
</ul>
<div my-customer="" element="data.element" element-array="data.elementArray" menu-elements="data.menuElements"></div>
</body>
JavaScript
angular.module('app', []).
controller('ctrl', ['$scope', function($scope) {
$scope.data = {
elementArray: [{
id: 'el1',
info: 'Element Info 1',
min: 0,
max: 9
}, {
id: 'el2',
info: 'Element Info 2',
min: 10,
max: 19
}],
menuElements: {
'el1': {
value: 1
},
'el2': {
value: 15
}
}
};
$scope.data.element = $scope.data.elementArray[0];
}]).
directive('myCustomer', function() {
return {
template: '<div class="well">' +
'<label for="{{element.id}}">{{element.info}}:</label>' +
'<input class="ui-slider" type="range" ng-model="testVar1" ng-change="changeValue(element.id)" name="{{element.id}}" min="{{element.min}}" max="{{element.max}}" id="{{element.id}}"/>' +
'<button ng-click="setTestValue()">Test</button>' +
'</div>',
scope: {
element: '=',
elementArray: '=',
menuElements: '=' // <= add menuElements to scope
},
controller: ['$scope', function($scope) {
$scope.setTestValue = function() {
$scope.menuElements[$scope.element.id].value = 5;
}
}],
link: function(scope, element, attr) {
scope.$watch(function() { // <= Watch changes of scope.menuElements[scope.element.id].value
return scope.menuElements[scope.element.id].value;
}, function(value) {
scope.testVar1 = value;
});
}
}
});
Plunker: http://plnkr.co/edit/FLl6pLBdCWAfTbvGSBxQ?p=preview
Edit:
If you need to modify scope from outside of Angular, you can still do it, by fetching scope of DOM element related to that scope:
function setValue() {
var scope = angular.element(document.getElementById('ctrl')).scope();
scope.$apply(function() {
scope.data.menuElements[scope.data.element.id].value = 7;
});
}
Plunker: http://plnkr.co/edit/LXwJEtCIxVNgAb5AdJM6?p=preview
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 have a select tag (to be used for country selection) which I want to prefill with options using a directive:
<select class="countryselect" required ng-model="cust.country"></select>
My directive goes like
return {
restrict : "C",
link: function postLink(scope, iElement, iAttrs) {
var countries = [
["AND","AD - Andorra","AD"],
["UAE","AE - Vereinigte Arabische Emirate","AE"]
... //loop array and generate opt elements
iElement.context.appendChild(opt);
}
}
I am able to fill the select with additional options, but the ng-model binding does not work. Even if cust.country has a value (e.g. "UAE"), the option is not selected.
How to make the select display the value of cust.country? If think I have some timing problem here.
You can use directive from Angular JS:
Markup:
<div ng-controller="MainCtrl">
<select ng-model="country" ng-options="c.name for c in countries"></select>
{{country}}
</div>
Script:
app.controller('MainCtrl', function($scope) {
$scope.countries = [
{name:'Vereinigte Arabische Emirate', value:'AE'},
{name:'Andorra', value:'AD'},
];
$scope.country = $scope.countries[1];
});
Check the docs of select: Angular Select
EDIT WITH DIRECTIVE
Directive:
app.directive('sel', function () {
return {
template: '<select ng-model="selectedValue" ng-options="c.name for c in countries"></select>',
restrict: 'E',
scope: {
selectedValue: '='
},
link: function (scope, elem, attrs) {
scope.countries = [{
name: 'Vereinigte Arabische Emirate',
value: 'AE'
}, {
name: 'Andorra',
value: 'AD'
}, ];
scope.selectedValue = scope.countries[1];
}
};
});
Main controller:
app.controller('MainCtrl', function($scope) {
$scope.country={};
})
Markup:
<div ng-controller="MainCtrl">
<sel selected-value="country"></sel>
{{country}}
</div>
Working Example: EXAMPLE
You need to add the options to the ngModelController so that this works. E.g. like this:
return {
restrict : 'C',
require: ['select', 'ngModel'],
link: function(scope, element, attrs, controllers) {
var countries = [['A', 'text text']];
countries.forEach(option => {
const optionElement = angular.element('<option/>')
.attr('value', option[0])
.text(option[1]);
element.append(optionElement);
controllers[0].addOption(option.value, optionElement);
});
}
};