Angular: Two way binding between Directive and Controller does not work - javascript

I am trying to build a Slider as an Angular directive. The Problem is, that I need the value of the Slider in my Controller, so I can send it via Json to a Service. The value is used in the Directive to update the length of a corresponding span and should get updated whenever the Slider is moved the value should be updated in the Directive and in the Controller. Right now it just updates the variable to the value I set in the Controller once and then it just stays as it is.
Controller:
angular.module('MyApp')
.controller('SliderController', ['$scope', '$http', function($scope, $http) {
$scope.m = 5;
$scope.m2 = 45;
}]);
Directive:
angular.module('MyApp')
.directive('slider', function ($rootScope) {
return {
replace: true,
restrict: 'E',
scope:{
value: '='
},
link: function (scope, element, attrs) {
scope.header = attrs.header;
scope.unit = attrs.unit;
scope.min = attrs.min;
scope.max = attrs.max;
scope.step = attrs.step;
// scope.value = attrs.value;
// scope.model = attrs.ngModel;
var calculation = function () {
// console.log(scope.value);
return scope.value / scope.max * 100;
};
scope.onChange = function () {
if( isNaN(scope.value) || parseInt(scope.value) > parseInt(scope.max)) {
scope.value = scope.max;
}
scope.width = calculation();
};
},
templateUrl: '../html/slider.html'
};
});
HTML Template
<div class="slider">
<h3>{{header}}</h3>
<div class="progress">
<div class="span-back">
<span style="width:{{width}}%"></span>
</div>
<input class="input-range" type="range" min="{{min}}" max="{{max}}" step="{{step}}" data-ng-model="model" data-ng-change="onChange()" >
</div>
<div class="input-group">
<input type="text" class="input-value" ng-model="model" data-ng-change="onChange()">
<div class="input-base">{{unit}}</div>
</div>
Index HTML
<div ng-controller="SliderController">
<slider id="floating-div" header="My Slider" min="1" max="250" step="1" unit="testunit" data-ng-model="m2" value="m2"></slider>
</div>

If it's the directive's controller, you should declare it as such. And use the bindToController too.
angular.module('MyApp')
.directive('slider', function ($rootScope) {
return {
controller: SliderController,
controllerAs: 'vm',
bindToController: {
value: '='
},
replace: true,
restrict: 'E',
scope: true,
....
See if that works.
Also, don't forget to import the slidercontroller.
import SliderController from './slider.controller.js';
(or whatever your path/name is)
Or if import is not available, write the controller directly into the same file as the directive. So the same as above, without the import, but add in the controller:
function SliderController($scope) {
... etc
}
Then the controller: SliderController; line in the directive should work.

Related

Passing in $scope.$on name parameter as an attribute of an AngularJS directive

I'm trying to create a directive which allows me to pass in an attribute string which I then use as the "name" parameter when subscribing to events using $scope.$on. Essentially, the series of events is this:
An object is broadcasted using $rootScope.$broadcast called 'validationResultMessage', in another controller for example.
I have a directive which has an attribute called "subscription" to which I pass the string 'validationResultMessage'.
That directive passes the value of the "subscription" attribute to its scope and subscribes to it with "$scope.$on".
The problem is, it looks like the value of the attribute is "undefined" at the time everything is evaluated, and so when I try to subscribe using $scope.$on, it actually subscribes me to "undefined" rather than "validationResultMessage"
Here is my directive:
app.directive('detailPane', function () {
return {
restrict: 'E',
scope: {
selectedItem: '=',
subscription: '#',
},
templateUrl: 'app/templates/DetailPane.html', //I'm also worried that this is causing my controller to get instantiated twice
controller: 'DetailPaneController'
};
});
which I then use like this:
<td class="sidebar" ng-controller="DetailPaneController" ng-style="{ 'display': sidebarDisplay }">
<detail-pane
selected-item='validationResult'
subscription='validationResultMessage'/>
</td>
And the controller that I'm trying to pass this attribute into:
app.controller('DetailPaneController', ['$scope', '$http', 'dataService', 'toastr', '$uibModal', '$rootScope', '$attrs', function ($scope, $http, dataService, toastr, $uibModal, $rootScope, $attrs) {
$scope.fetching = [];
$scope.validationResult = null;
$scope.sidebarDisplay = 'block';
console.log('subscription is ', $scope.subscription);
var thisSubscription = $scope.subscription;
//if I hardcode the param as 'validationResultMessage', this works
$scope.$on($scope.subscription, function (event, arg) {
$scope.validationResult = arg;
});
}]);
So another way that I managed to solve this particular issue is to only use the internal DetailPaneController as defined in the directive body. Part of my problem was that I was causing the controller to be instantiated twice by having it as both the parent controller using ng-controller= in my html as well as being defined in the directive body. This way I can just use the straightforward "#" binding and everything gets resolved in the right order. I can even have another directive within my template that I can pass my validationResult into.
The new setup looks like this:
DetailPaneController:
app.controller('DetailPaneController', ['$scope', '$http', function ($scope, $http) {
$scope.$on($scope.subscription, function (event, arg) {
$scope.validationResult = arg;
$scope.exception = JSON.parse(arg.Exception);
});
}]);
DetailPane Directive:
app.directive('detailPane', function () {
return {
restrict: 'E',
scope: {
subscription: '#' //notice I am no longer binding to validationResult
},
templateUrl: 'app/templates/DetailPane.html',
controller: 'DetailPaneController'
};
});
Directive as used in HTML:
<div class="sidebar" ng-style="{ 'display': sidebarDisplay }">
<detail-pane subscription='validationResultMessage' />
</div>
Directive Template (for good measure):
<div class="well sidebar-container">
<h3>Details</h3>
<div ng-show="validationResult == null" style="padding: 15px 0 0 15px;">
<h5 class=""><i class="fa fa-exclamation-triangle" aria-hidden="true" /> Select a break to view</h5>
</div>
<div ng-show="validationResult != null">
<table class="table table-striped">
<tr ng-repeat="(key, value) in validationResult">
<td class="sidebar-labels">{{key | someFilter}}</td>
<td >{{value | someOtherFilter : key}}</td>
</tr>
</table>
<another-directive selected-item="validationResult" endpoint="endpoint" />
</div>
I'm going to post my answer 1st, given that it's a bit of code, please let me know if this is the required outcome, so I can provide comments. You should be able to run the provided code snippet.
var app = angular.module('myApp', []);
app.directive('detailPane', function() {
return {
restrict: 'E',
transclude: false,
scope: {
selectedItem: '=',
subscription: '#'
},
link: function(scope, elem, attr) {
scope.$on(scope.subscription, function(e, data) {
scope.selectedItem = data.result;
elem.text(data.message);
});
},
};
});
app.controller('DetailPaneController', function($scope) {
$scope.validationResult1 = "";
$scope.validationResult2 = "";
});
app.controller('SecondController', function($rootScope, $scope, $timeout) {
$timeout(function() {
$rootScope.$broadcast('validationResultMessage1', {
message: 'You fail!',
result: 'Result from 1st fail'
})
}, 2000);
$timeout(function() {
$rootScope.$broadcast('validationResultMessage2', {
message: 'You also fail 2!',
result: 'Result from 2nd fail'
})
}, 4000);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app='myApp'>
<div ng-controller="DetailPaneController">
<detail-pane class='hello' selected-item='validationResult1' subscription='validationResultMessage1'></detail-pane>
<br/>
<detail-pane class='hello' selected-item='validationResult2' subscription='validationResultMessage2'></detail-pane>
<hr/>
<span>{{validationResult1}}</span>
<br/>
<span>{{validationResult2}}</span>
</div>
<div ng-controller="SecondController">
</div>
</body>
I think you should set watcher on $scope.subscription and checking if new value is set and then start subscribing passed event.
$scope.$watch('subscription', function(nv, ov){
//this makes sure it won't trigger at initialization
if(nv!==ov){
$scope.$on($scope.subscription, function (event, arg) {
$scope.validationResult = arg;
});
}
});

How to access isolated scope property from static directive template in Angular?

Controller:
app.controller('MainCtrl', ['$scope', function($scope) {
$scope.temp = {
'name': 'Test'
};
}]);
Template:
<custom-field ng-model="temp.name">
<md-input-container class="addon-menu">
<label>Name</label>
<input ng-model="ngModel" type="text" ng-focus="setLastFocusedElement($event)" />
</md-input-container>
</custom-field>
Directive:
app.directive('customField', function($timeout) {
return {
restrict: 'E',
scope: {
ngModel: '='
},
link: function($scope, $element, $attrs) {
console.log($scope.ngModel); // prints "test"
}
};
});
The problem is that once template is rendered, I can't see the value attached to input - it's empty, but I'm expecting to works, because inside link function it's printed correctly.
You are trying to access the directive scope in your template as the controller's scope. Move the markup inside the directive's template instead.
Directive:
app.directive('customField', function($timeout) {
return {
restrict: 'E',
scope: {
ngModel: '='
},
link: function($scope, $element, $attrs) {
console.log($scope.ngModel); // prints "test"
},
template: '<md-input-container class="addon-menu"><label>Name</label><input ng-model="ngModel" type="text" ng-focus="setLastFocusedElement($event)" /></md-input-container>'
};
Template:
<custom-field ng-model="temp.name"></custom-field>
You can also use separate html files as directive templates, which is good practise.
Are you trying to see the value in controller?
Please try $parent.$scope to see if value exist.

Angular isolated scope values not visible in template when using replace

I am creating a small app and I have the following directive with the template.
smallgrid.directive.js:
angular.module('myActions')
.directive('smallgrid', ['$rootScope', function($rootScope) {
return {
restrict: "E",
scope: {
actionable: "="
},
controller: function($scope) {
$scope.setLocation = function() {
console.log("yee");
};
}
};
}])
.directive('three', function() {
return {
replace: true,
templateUrl: '/app/my_actions/directives/templates/grid3x3.template.html'
};
})
.directive('four', function() {
return {
replace: true,
templateUrl: '/app/my_actions/directives/templates/grid4x4.template.html'
};
})
.directive('five', function() {
return {
replace: true,
templateUrl: '/app/my_actions/directives/templates/grid5x5.template.html'
};
});
grid3x3.template.html
<div class="k-edit-field" id="board">
<div class="row" ng-click="setLocation()">
{{actionable.probability}}
</div>
</div>
I use this directive as follows:
<smallgrid three actionable="currentAction.actionable" ng-if="somecondition"></smallgrid>
The UI renders properly. However it shows {{actionable.probability}} is empty and the Click event is not firing. However, if I remove the isolated scope and access the variable directly, values are available. I understand that when I am using isolated scopes, in the three directive, I can't access values of smallgrid. Is there a way to pass those values from smallgrid to the template?
Passing a directive as an attribute of a directive you're bound to have scope problems.
It will look better if you use scope inheritance for nested directives with ng-transclude.
So your starting point should be
<smallgrid actionable="currentAction.actionable" ng-if="somecondition">
<three></three>
</smallgrid>
This way <three> has access to the $parent
function smallgrid() {
return {
restrict: "E",
transclude: true,
scope: {
actionable: "="
},
template: `<div ng-transclude></div>`,
controller: function($scope) {
$scope.setLocation = function() {
console.log("yee");
};
}
};
}
function three() {
return {
template: `<div class="k-edit-field" id="board">
<div class="row" ng-click="$parent.setLocation()">
test = {{$parent.actionable.probability}}
</div>
</div>`
};
}
function myController($scope) {
$scope.currentAction = {actionable: {probability: "test"}};
$scope.somecondition = true;
}
angular.module('myApp', []);
angular
.module('myApp')
.controller('myController', myController)
.directive('smallgrid', smallgrid)
.directive('three', three);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myController">
<smallgrid actionable="currentAction.actionable" ng-if="somecondition">
<three></three>
</smallgrid>
</div>
</div>

Creating a custom attribute with AngularJS

I'm new to AngularJS. I'm trying to write a directive that will set the background-color of a <div> based on some scenario. Essentially, in my view, I want to be able to write this code:
<div effect-color="#2D2F2A">content here</div>
or
<div effect-color="{{effectColor}}">content here</div>
I know I need a directive. Currently, I'm doing this:
.directive('effectColor', [
function () {
return {
restrict: 'A',
controller: [
'$scope', '$element', '$attrs', '$location',
function ($scope, $element, $attrs, $location) {
// how do I get the value of effect-color here?
}
]
}
}
]);
I'm not sure how to get the value of the attribute itself. Do I need to add a scope? I just want the attribute value.
Thank you!
Here are two methods... First gets the attribute value through looking at the elements attribute value of your directive. The second gets passed the attribute value and attached to the isolated scope of your directive. Please note I have replaced your controller with a linking function. I suggest you give this article a read: https://docs.angularjs.org/guide/directive
Codepen: http://codepen.io/anon/pen/cGEex
HTML:
<div ng-app="myApp">
<div effect-color-one="#123456"></div>
<div effect-color-two="#123456"></div>
</div>
JavaScript:
angular.module('myApp', [])
.directive('effectColorOne', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
console.log('example 1: ' + attrs.effectColorOne);
}
}
}
)
.directive('effectColorTwo', function () {
return {
restrict: 'A',
scope: {
effectColorTwo: '#'
},
link:function (scope) {
console.log('example 2: ' + scope.effectColorTwo);
}
}
}
);
Another example
combining the above example and the ability to change the background colour of the element which the directive attribute resides is below:
Codepen: http://codepen.io/anon/pen/HospA
HTML:
<div ng-app="myApp">
<div effect-color="red">Hello</div>
</div>
JavaScript:
angular.module('myApp', [])
.directive('effectColor', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.css('background-color', attrs.effectColor);
}
}
}
);
You can get the value in your directive controller using $attrs parameter object
$attrs.effectColor // #2D2F2A
From the docs:
attrs is a hash object with key-value pairs of normalized attribute
names and their corresponding attribute values.
Also if you are going to modify the DOM (in your case applying background color) you should use link option.
DEMO
Seems like a duplicate of How to get attribute value of a custom tag in angularjs?
I think you need something like scope: { data: "=data" } in the definition of your directive
Please see here :http://jsfiddle.net/MP8ch/
<div ng-app="app">
<div ng-controller="firstCtrl">
<div effect-color="#fc9696">
<P>content here</P>
</div>
</div>
</div>
JS:
var app = angular.module('app', []);
app.directive('effectColor', function () {
return {
restrict: 'AE',
transclude: true,
// replace:'true',
scope: {
color: '#effectColor'
},
restrict: 'AE',
template: '<div style="background-color:{{color}}" ng-transclude></div>'
};
});
app.controller('firstCtrl', function ($scope) {
});
You can create an isolate scope and bind the attribute to it:
myApp.directive('effectColor', [
function () {
return {
restrict: 'A',
scope: {
effectColor: '='
},
link: function (scope, element, attrs) {
element.css({
color: scope.effectColor
});
},
controller: [
'$scope', '$element', '$attrs', '$location',
function ($scope, $element, $attrs, $location) {
console.log($scope.effectColor);
}]
}
}]);
http://jsfiddle.net/R7Rb6/
It's a scenario of a directive with itself as the attribute. See here that how can you actually get value in your directive.

how to use a function which returns value to ng-model using angularjs?

I wan to call a function which returns value and I want to use that value as ng-model.
the returned value from function should be displayed in a dialog.
I see my dialog empty - No Data.
here is my code:
my-dialog is a directive to show dialog which accepts templateurl and ng-model.
<my-dialog my-dlg-template-url="/app/ScheduleDlg.html" ng-model="ViewSchedule">
<button ng-click="openDialog()">Schedule</button>
</my-dialog>
here is the function to be called in ng-model.
$scope.ViewSchedule = function () {
console.log('ViewSchedule function call');
.....
return obj.Schedule();
};
here is the directive:
return {
require: 'ngModel',
replace: true,
transclude: false,
priority: 100,
restrict: 'E',
scope: true,
link: function ($scope, $element, $attrs, $ctrl) {
var getDialogTemplate = $attrs.myDlgTemplateUrl;
$scope.openDialog = function (confirmationAction) {
var modalInstance = $modal.open({
backdrop: 'static',
templateUrl: getDialogTemplate,
controller: "myDialogCtrl",
resolve: {
dialogData: function () {
return {
dlgData: $attrs.ngModel
};
}
}
});
modalInstance.result.then(function () {
return confirmationAction();
});
};
}
};
}])
.controller('myDialogCtrl', ['$scope', '$modal', '$modalInstance', 'dialogData', function ($scope, $modal, $modalInstance, dialogData) {
$scope.dialogData = dialogData.dlgData;
$scope.onOk = function () {
$modalInstance.close();
};
$scope.onCancel = function () {
$modalInstance.dismiss('cancel');
};
}])
Here is the dialog template :
<tr ng-repeat="item in dialogData">
<td>{{item.$index}}</td>
<td>{{item.StartDate}}</td>
<td>{{item.EndDate}}</td>
</tr>
ng-model establishes a 2 way data binding only needed if you are planning to alter the model if not a simple scope definition with one way binding or a reference bound would do it
you currently have
scope:true
you can do
scope:{
watchfor:'#result'
}
and inside the directive set a watcher
scope.$watch('watchfor',function(val){})
in your direcitve declaration you could have.
<my-dialog my-dlg-template-url="/app/ScheduleDlg.html" result="ViewSchedule()"> </my-dialog>
and set this in your directive template
<button ng-click="openDialog()">Schedule</button>
unless you want it linked to your parent scope.

Categories