Pass directives through another directive - javascript

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.

Related

AngularJS Bound Ng-Model Inside Directive Has Wrong Attribute Name

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.

Directive scope

I have a directive in angular and a form which is bound to a scope variable. Code:
<my-directive></my-directive>
<form>
<input ng-model="username" required>
</form>
Directive:
.directive('myDirective', function(){
restrict : 'E',
controller: function($scope){
console.log($scope.username); // Displays user name, same scope!!!
},
link: function(scope, element, attrs){
// Other codes
},
templateUrl: "templates/my-template-url.html"
}
})
The username variable can be reached inside the controller in my directive. This is not why I expected since I close the directive and they shouldn't share the same scope?
Why does this work?
Well you are using scope:false . It can access and change it's parent scope.
So your node scope doesn't matter here because username is in it's parent scope.
<my-directive></my-directive>
<form>
<input ng-model="username" required>
</form>
If your don't wanna to access parent scope then create an isolate scope
Like this
.directive('myDirective', function(){
restrict : 'E',
scope : {},
controller: function($scope){
console.log($scope.username); // Displays user name, same scope!!!
},
link: function(scope, element, attrs){
// Other codes
},
templateUrl: "templates/my-template-url.html"
}
})

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

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