Set ng-model and class property inside a directive template - javascript

I am trying to create a simple directive to make my html page abit slimmer. I want to pass down properties to the directive template and set properties.
The problem is when i am trying to pass a string and set the ng-model property and class property in the template. All the other properties i pass down work fine its just the class and ng-model property that doesnt work.
How can i set the ng-model and class property in my template by just passing down a string to them?
controller:
angular.module('app.sst').directive('inputDirective', function () {
return {
scope: {
colWidth: '#',
label: '#',
type: '#',
name: '#',
ngModel: '='
},
templateUrl: 'components/blocks/directives/inputTemplate.html',
};
});
directive template:
<div class="{{colWidth}}">
<label>{{label}}</label>
<div class="field">
<input type="{{type}}"
name="{{name}}"
ng-model="ngModel"
class="form-control inputText" />
</div>
</div>
index:
<div input-directive
colWidth="col-md-4"
label="Proceded by"
type="text"
name="procededBy"
ng-model="vm.SSTItem.procededBy">
</div>

Two things to take note here:
1) Make ngModel required.
2) Do not use ngModel in isolated scope the way you are using now as angular might get confused for the key instead use meaningful name like below.
angular.module('app', [])
.directive('inputDirective', function(){
return {
required: '^ngModel',
scope: {
modelName: "=ngModel"
},
template: '<input type="text" ng-model="modelName">'
};
})
See plunk.

Related

How can I manually propagate a value to the directive's parent scope in AngularJS?

My AngularJS typeahead FaveDirective needs to bind a single value to the parent scope, and call an update function when that value changes:
Parent html:
<div class="parent-controller-scope ng-scope">
<my-fave-picker favorite="parent.favorite" on-change="parent.update()">
</div>
Fave picker directive template:
<input
type="text"
ng-model="vm.favorite"
typeahead-on-select="vm.onChange()"
ng-init="vm.loadTypeaheadValues()"
placeholder="Pick your favorite Swift"
uib-typeahead="name for name in ::vm.TypeaheadValues | filter:$viewValue"
class="form-control">
Fave picker directive code:
(function (angular, _) {
'use strict';
angular
.module('favorite')
.directive('MyFavePicker', function() {
return {
restrict: 'E',
templateUrl: 'fave-picker-template.html',
scope: {
favorite: '=',
onChange: '&'
},
controllerAs: 'vm',
bindToController: true,
controller: 'FavePickerController'
};
})
.controller('FavePickerController', function() {
// etc.
});
}(angular, _));
This works almost correctly; when the typeahead input is committed, it calls update() on the parent scope as intended. The problem is that this happens before the latest value of favorite is propagated to the parent scope. In other words, if the typeahead has possible values ["Taylor Swift", "Jonathan Swift"] and I type "Tay" and then hit enter to select the value from the dropdown, then at the time the typeahead-on-select callback is executed, I have the following values:
vm.favorite = "Taylor Swift"
parent.favorite = "Tay"
The parent.update() function therefore operates with the wrong value of parent.favorite ("Tay" instead of "Taylor Swift").
I can think of some bad ways, but what's the right way to do this so that the change to vm.favorite gets propagated back to the parent scope before calling parent.favorite()?
Note that the following things are not possible in my circumstances:
inheriting parent scope instead of using isolate scope
passing favorite as an argument to update (on-change="parent.update(favorite)")
setting a timeout in dir.onChange() before calling parent.update()
Avoid using two-way, '=', binding in components to propagate values. Instead use one-way, '<', binding for inputs and expression, '&', binding for outputs:
<my-fave-picker favorite="parent.favorite"
on-change="parent.favorite=$value; parent.update($value)">
</my-fave-picker>
app.directive('MyFavePicker', function() {
return {
restrict: 'E',
templateUrl: 'fave-picker-template.html',
scope: {
̶f̶a̶v̶o̶r̶i̶t̶e̶:̶ ̶'̶=̶'̶,̶
favorite: '<',
onChange: '&'
},
controllerAs: 'vm',
bindToController: true,
controller: 'FavePickerController'
};
})
In the component template:
<input
type="text"
ng-model="vm.favorite"
ng-change="vm.onChange({$value: vm.favorite})"
typeahead-on-select="vm.onChange({$value: vm.favorite})"
ng-init="vm.loadTypeaheadValues()"
placeholder="Pick your favorite Swift"
uib-typeahead="name for name in ::vm.TypeaheadValues | filter:$viewValue"
class="form-control"
/>
By applying the value in the expression, '&', binding, the value is propagated immediately. With two-way, '=', binding, the value is propagated after a digest cycle.
For more information, see
AngularJS Comprehensive Directive API Reference - scope
AngularJS Developer Guide - Component Based Application Architecture

AngularJS - Directive's class name cannot bring into inner template

I want to make a directive which take a class name conditionally. However, I found that my code can work only if I hardcode the class name into the class attribute. If I try to use it with any expression, its failed to work.
For e.g.
// HTML
// Doesn't work (cannot find class="editable" in the final output template)
<tit-txt ng-class="true ? 'editable' : ''" ng-model="mdEnt.phone"></tit-txt>
// Works! (I can find class="editable" in the final output template)
<tit-txt class="editable" ng-model="mdEnt.phone"></tit-txt>
//JS
.directive('titTxt', function () {
return {
restrict: 'E',
scope: {
ngModel: '=',
},
link: function (scope, element, attrs) {
scope.editable = element.hasClass('editable') ? 'editable' : '';
},
template: '<input ng-class="editable" ng-model="ngModel" />',
};
})
Anyone can explain to me that why is it happening? How can I use it with expression?
UPDATE 1
// HTML
// Doesn't work
<tit-txt ng-class="{'editable': true}" ng-model="mdEnt.phone"></tit-txt>
//JS
.directive('titTxt', function () {
return {
restrict: 'E',
scope: {
title: '#',
fieldName: '#',
ngModel: '=',
},
link: function (scope, element, attrs) {
console.log(element.hasClass('editable'));
scope.editable = element.hasClass('editable') ? 'editable' : '';
},
template: '<div><span>{{title}}: </span><input id="{{fieldName}}" ng-class="{editable: true}" name="{{fieldName}}" ng-model="ngModel" /></div>',
};
})
Anyone can explain to me that why I get false in the console.log(element.hasClass('editable'));?
With the class attribute, the element's class is set by JavaScript. With the ng-class directive, the class is set by the AngularJS framework. When there are more that one directive on an element, there is no guarantee of the order of execution of the code of the respective directives.
Avoid having AngularJS manipulate the DOM and having subsequent AngularJS manipulate the model based on the state of the DOM. With MVC frameworks the Model should be the single source of truth and the DOM should be directly determined by the Model.
<tit-txt inner-class="true ? 'editable' : ''" my-model="mdEnt.phone">
</tit-txt>
app.directive('titTxt', function () {
return {
restrict: 'E',
scope: {
title: '#',
fieldName: '#',
innerClass: '<',
myModel: '=',
},
link: function (scope, element, attrs) {
scope.$watch(attrs.innerClass, function(newValue) {
console.log("inner-class=", newValue);
});
},
template: `<div><span>{{title}}: </span>
<input id="{{fieldName}}" ng-class="innerClass"
name="{{fieldName}}" ng-model="myModel" />
</div>`,
};
})
Notice how the directive uses one-way, '<', binding to compute the value of the inner-class attribute from an AngularJS Expression.
Also notice that I changed ng-model to my-model. The ng- prefix is reserved for core AngularJS directives. Use of ng-model should be specifically be avoided unless the custom directive properly integrates with the ngModelController.

Ng-repeat not passing down certain attributes without quotes to scope

I am attempting to pass down attributes returned from a MongoDB query to a directive in Angular, but for some reason it will not pass down an "_id" attribute. When I view the records returned in the parent's $scope, I can see that each object does in fact have an "_id". However when I go to inspect the $scope of the directive I am attempting to render, I can see that it has every attribute besides "_id".
My parent template code:
<div id=cards>
<div ng-repeat='card in cards' class='card'>
<card-directive
_id={{card._id}} attack={{card.attack}} cost={{card.cost}}
health={{card.health}} img={{card.img}} name={{card.name}}
hero={{hero}} rarity={{card.rarity}} type={{card.type}}
class={{hero}} add={{add}} faction={{card.faction}} >
</card-directive>
</div>
</div>
My directive code:
function cardDirective() {
return {
restrict: 'E',
scope: {
'_id': '#',
'img': '#',
'attack': '#',
'cost': '#',
'health': '#',
'name': '#',
'rarity': '#',
'type': '#',
'hero': '#',
'add': '#',
'faction': '#'
},
Is there something special about including an attribute that begins with an underscore?
It should have worked, only wrap your attribute value with "(quotes)
Rather than passing each value inside attribute, you could consider passing whole card object to directive.
<div id=cards>
<div ng-repeat='card in cards' class='card'>
<card-directive my-card="card"></card-directive>
</div>
</div>
Directive
function cardDirective() {
return {
restrict: 'E',
scope: {
'myCard': '<'
},
So inside directive you could easily access value inside card object and < ensure one way binding. You could use {{myCard._id}} for using _id.
AngularJS is normalizing the attribute _id to Id with a capital I.
For more information, see AngularJS Developer Guide - Directive Normalization.
angular.module("app",[])
.directive("customDirective", function() {
return {
template: `Id={{Id}}`,
link: function(scope, elem, attrs) {
scope.Id = attrs.Id;
console.log("Id=",attrs.Id);
}
}
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<div custom-directive _id=99></div>
</body>

Dynamic model binding in nested directives working for input values, but not select

I have a custom directive being used like so:
<drop-down-filter-field
model="my_model.property"
options="{ list: property_options, field: 'code' }"
></drop-down-filter-field>
With the directive code (field is another directive that groups together some common markup for errors and layout etc):
<field label="label" field="field" model="model" display="display" annotation="annotation">
<select
ng-model="$parent.model"
ng-change="setValue(item.value, item[options.field])"
>
<option ng-repeat="item in options.list" value="{{item.value}}">{{item[options.field]}}</option>
</select>
</field>
I tried to add the ng-change to manually set my model value, since the drop down isn't working. But, the function in ng-chang isn't firing either.
And Directive
angular.module('my-app')
.directive('dropDownFilterField', function($compile) {
return {
restrict: 'E',
templateUrl: 'views/directives/form-elements/drop-down-filter-field.html',
controller: 'dropDownFilterField',
scope: {
model: '=',
label: '=',
field: '=',
options: '=',
annotation: '=',
}
};
})
.controller('dropDownFilterField', function($scope, $element, $parse) {
$scope.setValue = function(value) {
self.$scope.model = value;
};
})
;
I already have this pattern with the nested directive with "$parent.model", functioning in several other templates/directives, for example, an input tag:
<field label="label" field="field" model="model" annotation="annotation" validate="validate">
<input type="text" name="{{formname}}" ng-model="$parent.model" ng-required="{{required}}" placeholder="{{field.default}}"
uib-typeahead="{{'typeAheadItem' + (options.field ? '.' + options.field : '')}} as {{'typeAheadItem' + (options.field ? '.' + options.field : '')}} for typeAheadItem in options.list | filter:$viewValue | limitTo:{{options.limit || 8}}"
ui-validate="'validateFunction($value)'"/>
</field>
All of my other custom directives that contain inputs, and textareas are working, but I can't get it working for select. Anyone know why this might be ?
Have you tried using ng-options?
<select
ng-model="$parent.model"
ng-options="item.value as item[options.field] for item in options.list"
>
</select>
There should be no need to use ng-change to update ng-model value, the two-way-binding of ng-model should update itself.

Pass ng-model as argument in directive angualjrs

I am writing a directive in AngularJs and I want to pass ng-model as an argument.
<div class="col-md-7"><time-picker></time-picker></div>
The directive is:
app.directive('timePicker', function () {
return {
restrict: 'E',
replace: true,
template: '<input type="text" class="form-control time-picker" ng-model="emp.signin">',
link: function ($scope, element, form) {
$(element).timepicker({'timeFormat': 'H:i:s'});
}
}
})
It is working fine, and here the ng-model is emp.signin. I want to be able to pass this ng-model dynamically as argument
How is this possible?
You can use
<div class="col-md-7"><time-picker model-value="emp.signin"></time-picker></div>
Angular
app.directive('timePicker', function () {
return {
restrict: 'E',
replace: true,
template: '<input type="text" class="form-control time-picker"ng-model="modelValue ">',
scope: {
modelValue : '=',
}
link: function ($scope, element, form) {
$(element).timepicker({'timeFormat': 'H:i:s'});
}
}
})
Explaination
The “=” prefix will create a two-way binding between the parent and
directive scope and it’ll always expect the attribute value to be the
model name which means you cannot provide an expression as the value
of attribute mapped to “=” prefix.
For reference: "http://www.undefinednull.com/2014/02/11/mastering-the-scope-of-a-directive-in-angularjs/"

Categories

Resources