AngularJS Bound Ng-Model Inside Directive Has Wrong Attribute Name - javascript

This is a followup to a previous question. I have some questions on the rendering of a custom directive and the actual output in the DOM when the page is rendered.
Here is the definition of my directive:
angular.module('moduleName)
.directive('selectValue', ['$timeout', function($timeout) {
const directive = {
restrict: 'E',
replace: true,
scope: {
controlId: '#',
model: '=?'
},
controller: 'selectValueCtrl',
templateUrl: 'template.html'
};
return directive;
}
Here is the externalized template:
<!-- template.html -->
<input id="{{controlId}}" name="{{controlId}}" placeholder="Enter Value"
type="text" ng-model="model" />
Given the following use of the directive:
<select-value controlId="selectValue" model="data.value"></selectValue>
Why does it render as the following:
<input id="selectValue" ng-model="model" />
Instead of:
<input id="selectValue" ng-model="data.value" />
Did I make a mistake in my code, or is this expected behavior?

{{controlId}} - this is an interpolation and Angular should calculate the expression behind the braces. That's why you get id="selectValue" instead of id="{{controlId}}"
ng-model="model" it's just a two-way binding which is handled by Angular under the hood, but Angular doesn't change the template in this case. Angular should know what model is binded, values are transferred behind the scenes.

Related

Pass directives through another directive

We created some directives to speed up our coding and clean the htmls, for example:
<div class="form-group">
<label class="form-label">{{label}}
<input ng-disabled="ngDisabled" ng-model="ngModel" type="text" class="form-control"/>
</label>
<div ng-if="invalidMessage" class="error">{{invalidMessage}}</div>
</div>
and the js
angular.module('ui.default-input', [])
.directive('defaultInput', function(){
return {
restrict: 'E',
templateUrl: '_component.html',
scope: {
label: '#',
invalidMessage: '#',
ngModel: '=',
ngDisabled: '='
}
}
});
So we can use it anywhere like
<default-input label="im a label" ng-model="vm.model"></default-input>
The problem is, whenever we want to add another directive to the input, we have to defined it in the directive own scope and manually apply it. (Like we did with ngDisabled and ngModel).
Is there a smarter solution? Is it wrong to use directives in that way?
Thanks in advance.

Angular: custom directive not updating controller

I started to learn Angular not so long time ago and I'm trying to understand scope, binding and etc.
I have an order details controller:
orderApp.controller('OrderDetailsController', ['$http','$routeParams','$scope','config', function($http, $routeParams, $scope, config){
var orderCtrl = this;
orderCtrl.orderId = $routeParams.orderId;
orderCtrl.order = {};
orderCtrl.editingView = false;
...
}]);
On order details page I want to output all information about selected order. Also we need to give user ability to edit order. Information about editing mode is stored in orderCtrl.editingView.
I decided to create custom directive. If edit mode is off - display text, otherwise display input.
orderApp.directive('editableText', function(){
return {
restrict: 'E',
scope: {
property: '=property',
editMode: '=editMode'
},
controller: 'OrderDetailsController',
controllerAs: 'orderCtrl',
templateUrl: '/pages/editable-text.html'
}
});
This is template:
<div class="col-xs-8" ng-if="!editMode">{{property}}</div>
<div class="col-xs-8" ng-if="editMode"><input type="text" class="form-control" ng-model="property"></div>
And this is how I use directive in html files:
<editable-text property="orderCtrl.order.coid" edit-mode="orderCtrl.editingView"></editable-text>
Text and input are switching when edit mode is on/off. Problem is that orderCtrl.order.coid property is not updated when I change it in input.
Before edit property looks like:
Turn on edit mode and change value:
Turn off edit mode and we see old value:
Do I need to synchronise controller values and directive scope? I thought that with 2-ways binding it should happen automatically. Probably there is any other way to write this functionality? Will appreciate any help.
UPD
Directive code:
orderApp.directive('editableText', function(){
return {
restrict: 'E',
bindToController: {
property: '=property',
editMode: '=editMode'
},
controller: 'OrderDetailsController',
controllerAs: 'orderCtrl',
templateUrl: '/pages/editable-text.html'
}
});
Directive template:
<div class="col-xs-8" ng-if="!orderCtrl.editMode">{{orderCtrl.property}}</div>
<div class="col-xs-8" ng-if="orderCtrl.editMode"><input type="text" class="form-control" ng-model="orderCtrl.property"/></div>
Directive usage:
<editable-text property="orderCtrl.order.coid" edit-mode="orderCtrl.editingView"></editable-text>
I'm not sure that we really need to pass edit-mode attribute.
You should use bindToController: { ..scope properties.. } option here inside your directive to make sure that isolated scope properties should get bounded to controller this context.
Directive
orderApp.directive('editableText', function(){
return {
restrict: 'E',
bindToController: {
property: '=property',
editMode: '=editMode'
},
controller: 'OrderDetailsController',
controllerAs: 'orderCtrl',
templateUrl: '/pages/editable-text.html'
}
});
Template
<div class="col-xs-8" ng-if="!orderCtrl.editMode">
{{orderCtrl.property}}
</div>
<div class="col-xs-8" ng-if="orderCtrl.editMode">
<input type="text" class="form-control" ng-model="orderCtrl.property"/>
</div>
Note:- this above bindToController: { ..scope properties.. } option available for angular 1.4+ versions.
For Angular 1.3 > version & 1.4 > version you should use former way of doing it by having bindingToController: true to bind scope variable to controller context & do keep the varaibles inside scope: { ...props... }
scope: {
property: '=property',
editMode: '=editMode'
},
bindToController: true

Updating ngModel of custom element from inside angular controller instead of Angular directive

I have a directive something like the following for a custom control
myApp.directive('myControl', function ($filter) {
return {
restrict: 'E',
scope: { ngModel: "=" },
templateUrl: 'some template path',
controller: 'myControlController'
}
});
I define the all the functionality of this control in a separate controller block:
myApp.controller('myControlController',['$scope', function($scope){
$scope.value=$scope.ngModel;
//$scope.ngModel='newValue';
}]);
I have a parent controller within which I create a myControl as follows:
myApp.controller('componentController',['$scope', function($scope){
$scope.myModelValue='OriginalValue';
}]);
The following is the html content of the parent page:
<html>
<body>
<div ng-controller="componentController">
<my-control ng-model="myModelValue"></my-control>
{{myModelValue}}
</div>
</body>
</html>
Now, I wish to update the value of the ngModel to 'newValue' from the original value of 'OriginalValue' using something like
//$scope.ngModel='newValue';
Is there any way to achieve this other than doing it from a directive or using a $parent operation.

Directive cannot replace variable in template: Syntax Error

I created a directive which has a directive inside it, and when I try to give it parameters I get this message: This error message
Here is my directive
app.directive('percentageSquare', function(){
return {
restrict: 'E',
scope: {
color: '#',
chartConfig: '#'
},
templateUrl: '/templates/dashboard/charts/PercentageChart.html'
};
});
Which loads this template:
<div class="drop-shadow">
<highchart id="{{chartConfig}}" config="{{chartConfig}}" style="background-color: {{color}};" ng-show="true"></highchart>
</div>
Here is how I am calling it:
<percentage-square color="yellow" chart-config="chartBounceRate"></percentage-square>
I am not sure what I can do to fix it, because it look just fine to me...
The way highcharts works, it want to have config to be an object from scope. so you should use two way binding here
Directive
scope: {
color: '#',
chartConfig: '=', //<--two way binding here
selector: '#'
},
Directive Use
<percentage-square color="'yellow'" chart-config="chartBounceRate" selector="{{selector}}"></percentage-square>
Template
<div class="drop-shadow">
<highchart id="{{selector}}" config="chartConfig"
ng-style="{background-color: color}" ng-show="true">
</highchart>
</div>
EDIT
See this famous stackoverflow question and answer to understand #, & and = in angularjs directive definition

Create Isolated Scope using Directive

I have a set of clients, displayed on a form, which must have individual scopes (per client):
View plunker here.
Naturally, I would expect that creating a new directive, with isolate scope, would not allow elements to be bound to oustide $scope, using a custom directive like this:
<fieldset client="156510">
<legend>Client 156510</legend>
<!-- Form elements -->
</section>
</fieldset>
And likewise:
angular.module("plunker", [])
.controller("ClientCtrl", function($scope) {})
.directive("client", function() {
return {
restrict: "A",
scope: {
name: "#name",
client: "=client"
}
};
});
Given that ng-repeat is not an option, how can I isolate scope of any contained HTML using a directive? The angular docs seem to suggest this is possible, but my implementation does not seem to work as intended.
Thanks in advance.
Any directive on an element with isolated scope WITHOUT template/templateUrl actually does NOT get a new scope.
Here is the proof
http://plnkr.co/edit/jXwrtG?p=preview
.directive("client", function() {
return {
restrict: "A",
template: ' ',//notice extra spaces
replace: true,//notice this
scope: {
name: "#name",
client: "=client"
}
};
});
Also scope=true will solve your problem.
http://plnkr.co/edit/JdiCVV?p=preview
.directive("client", function() {
return {
restrict: "A",
scope: true
};
});
Also as pointed by #imscrb
transclude = true also works but you must add ng-transclude to the element
<fieldset client="156510" ng-transclude>
http://plnkr.co/edit/b0hX5h?p=preview
.directive("client", function() {
return {
restrict: "A",
transclude: true,
scope: {
name: "#name",
client: "=client"
}
};
});
If you want each client to be totally isolate then you need to put all of the HTML being used within the template of the directive. this is because the current html you have, and the model's your binding are controlled by the controller and not the directive
I.E... (haven't tested with your code, but its what you need to do) - it's likely you need to change the ng-model in your template to the model you will push into the directive though - this is just as an example
return{
....
template: '<legend>Client 156510</legend>'
+ '<section>'
+ '<div class="horizontal-field">'
...........
+ '</section>'
}
then your html would simply be something like (again, psuedo-code):
<fieldset client="156510" ng-model="yourModel"></fieldset>
If i understand your question correctly then enabling transclusion should solve your problem:
angular.module("plunker", [])
.controller("ClientCtrl", function($scope) {})
.directive("client", function() {
return {
restrict: "A",
scope: {
name: "#name",
client: "=client"
},
transclude: true,
template: '<div ng-transclude></div>'
};
});
I believe that you do things in wrong way since uou broke the DRY principle. Instead of repeating HTML for each client in view, move it into directive's template and pass into directive isolated scope client themselve. Of course, you can pass into directive only client's id and get client from some service just in directive if you want.
And don't specify all Angular service classes manually like ng-pristine, ng-dirty etc.
Controller and directive
app = angular.module("plunker", []).controller("ClientCtrl", function($scope) {
$scope.clients = [{
id: 12345,
firstName: 'First',
lastName: 'First',
middleInitial: 'A'
}, {
id: 123456,
firstName: 'Second',
lastName: 'Second',
middleInitial: 'B'
}];
})
.directive("client", function() {
return {
restrict: "A",
templateUrl: 'client-form.html',
scope: {
client: "=client"
}
};
});
Client form template
<fieldset ng-form='clientForm'>
<legend>Client {{client.id}}</legend>
<section>
<div class="horizontal-field">
<label for="title">Title</label>
<select ng-model="client.title" name="title">
<option value=""></option>
<option value="mr">Mr.</option>
<option value="mrs">Mrs.</option>
<option value="ms">Ms.</option>
<option value="miss">Miss</option>
<option value="dr">Dr.</option>
</select>
</div>
<div class="horizontal-field">
<label for="first-name">First Name</label>
<input placeholder="First Name" required="" autofocus="" ng-model="client.firstName" name='first_name' type="text" >
</div>
<div class="horizontal-field">
<label for="middle-initial">Middle Initial</label>
<input maxlength="1" ng-model="client.middleInitial" type="text" name="middle_initial" value="" id="middle-initial">
</div>
<div class="horizontal-field">
<label for="last-name">Last Name</label>
<input placeholder="First Name" ng-model="client.lastName" type="text" name="last_name">
</div>
</section>
</fieldset>
View
<div ng-repeat='client in clients' client='client' ></div>
Plunker

Categories