Data binding not working in an event in directive - angularjs

I cannot get the data binding to work in this directive. The variables are not bound properly when I change them inside the event handler,
What am I doing wrong? > Test Fiddle
var myApp = angular.module('myApp', [])
.directive('inputTest', function () {
return {
restrict: 'E',
template: '<div class="input-group">\
<input type="text" class="form-control" />\
<br>child scope: {{myValue}}\
</div>',
scope: {
myValue: '=',
},
link: function (scope, element, attrs) {
$(element).on('click', function (e) {
alert('c');
scope.myValue = 'clicked';
});
scope.myValue = 'not clicked';
},
};
})
function MyCtrl($scope) {
$scope.myValue = 'parent value';
}
HTML
<div ng-controller="MyCtrl">parent scope: {{myValue}}
<input-test my-value="myValue"></input-test>
</div>

Do not forget to call $scope.$apply() at the end of the event handler.
First level bindings may not work as expected due to how prototypical inheritance works. If you try the first point and still get no results, try putting myValue a level deeper:
$scope.data.myValue = 'parent value';
and:
<input-test my-value="data.myValue"></input-test>

Related

How to make my own ng-change and ng-model in a directive?

I am trying to implement a directive with its own model and change attribute (as an overlay for ng-model and ng-change). It works apparently fine but when the function of the father scope is executed and some variable of the scope is modified in it, it is delayed, the current change is not seen if not the one executed in the previous step.
I have tried adding timeouts, $apply, $digest ... but I can not get it synchronized
angular.module('plunker', []);
//Parent controller
function MainCtrl($scope) {
$scope.directiveValue = true;
$scope.textValue = "init";
$scope.myFunction =
function(){
if($scope.directiveValue === true){
$scope.textValue = "AAAA";
}else{
$scope.textValue = "BBBB";
}
}
}
//Directive
angular.module('plunker').directive('myDirective', function(){
return {
restrict: 'E',
replace: true,
scope: {
myModel: '=model',
myChange: '&change'
},
template: '<span>Check<input ng-model="myModel" ng-change="myChange()"
type="checkbox"/></span>',
controller: function($scope) {
},
link: function(scope, elem, attr) {
var myChangeAux = scope.myChange;
scope.myChange = function () {
setTimeout(function() {
myChangeAux();
}, 0);
};
}
});
// Html
<body ng-controller="MainCtrl">
<my-directive model="directiveValue" change="myFunction()"></my-directive>
<div>Valor model: {{directiveValue}}</div>
<div>Valor texto: {{textValue}}</div>
</body>
The correct result would be that the "myFunction" function runs correctly
Example: https://plnkr.co/edit/q3IqRCIhwLChlGrkDxyO?p=preview
You should use AngularJS' $timeout which is a wrapper for the browser default setTimeout and internally calls setTimeout as well as $digest, all at the right time in the execution.
Your directive code should change as such:
angular.module('plunker').directive('myDirective', function($timeout){
return {
restrict: 'E',
replace: true,
scope: {
myModel: '=model',
myChange: '&change'
},
template: '<span>Check<input ng-model="myModel" ng-change="myChange()" type="checkbox"/></span>',
controller: function($scope) {
},
link: function(scope, elem, attr) {
var myChangeAux = scope.myChange;
scope.myChange = function () {
$timeout(myChangeAux, 0);
};
}
};
});
Docs for AngularJS $timeout

How to access ng-model value in directive?

I created a directive for google map auto-complete. everything is working fine, but the problem is when I need to access the value of input and re-set it. it doesn't work. Here is code:
<div controller='mainCtr'>
<span click='reset(destination)'>Reset</span>
<div class='floatleft' style='width:30%;margin-right:40px;'>
<smart-Googlemaps locationgoogle='destination.From'></smart-Googlemaps>
<label>From</label>
</div>
</div>
In the directive:
angular.module('ecom').directive('smartGooglemaps', function() {
return {
restrict:'E',
replace:false,
// transclude:true,
scope: {
locationgoogle: '='
},
templateUrl: 'components/directives/autocomplete/googlemap-search.html',
link: function($scope, elm, attrs){
var autocomplete = new google.maps.places.Autocomplete($(elm).find("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
// $scope.location = place.geometry.location.lat() + ',' + place.geometry.location.lng();
// console.log(place);
$scope.locationgoogle = {};
$scope.locationgoogle.formatted_address = place.formatted_address;
$scope.locationgoogle.loglat = place.geometry.location;
$scope.locationgoogle.locationText = $scope.locationText;
$scope.$apply();
});
}
}
})
Here is html for directive:
<input id="google_places_ac" placeholder="Please enter a location" name="google_places_ac" type="text" class="input-block-level" ng-model='locationText'/>
The directive works fine, I create a isolated scope(locationgoogle) to pass the information I need to parent controller(mainCtr), now in the mainCtr I have a function calld reset(), after I click this,I need to clean up the input make it empty. How Can I do it?
One way to access the value of the model in your directive from a parent controller is to put that on the isolate scope too and use the two-way binding flag = like you've done with the locationgoogle property. Try this:
DEMO
html
<body ng-controller="MainCtrl">
<button ng-click="reset()">Reset</button>
<smart-googlemaps location-text="locationText"></smart-googlemaps>
</body>
js
app.controller('MainCtrl', function($scope) {
// need to define model in parent and pass to directive
$scope.locationText = {
value: ''
};
$scope.reset = function(){
$scope.locationText.value = '';
}
});
app.directive('smartGooglemaps', function() {
return {
restrict:'E',
replace:false,
// transclude:true,
scope: {
locationgoogle: '=',
locationText: '='
},
// ng-model="locationText.value"
template: '<input id="google_places_ac" placeholder="Please enter a location" name="google_places_ac" type="text" class="input-block-level" ng-model="locationText.value"/>',
link: function($scope, elm, attrs){
// implement directive googlemaps logic, set text value etc.
$scope.locationText.value = 'foo';
}
}
})

How to pass data from directive to controller?

I am currently working on integrating a plugin in my angular application and I'm trying to convert it into a directive. I have fiddled around with the events and methods of the plugin but failed to get the desired results. here's my code:
HTML
<div class="input-daterange datepicker full" rangepicker ng-model="packer.selected.date">
<i class="fa fa-calendar"></i>
<div class="inputs datepicker">
<input
ng-model="packer.selected.date.start"
name="start"
value="<% packer.initData.dateStart %>">
<span class="add-on">-</span>
<input
ng-model="packer.selected.date.end"
name="end"
value="<% packer.initData.dateStart %>">
</div>
</div>
Javascript:
Application.directive('rangepicker', function() {
return {
restrict: 'A',
link: function(scope, element, attrs, ngModel) {
$(element).datepicker({
format: 'yyyy-mm-dd'
});
$(element).on('changeDate', function(){
/*
$(element).find('input').each(function(){
$(this).trigger('input');
}) */
})
}
};
});
Application.controller('PackingStatisticsController', ['$scope', '$http', 'initData', function($scope, $http, initData) {
var packer = this;
packer.initData = initData;
packer.selected = {
date : {
start : "",
end : ""
},
user : ""
}
packer.log = function()
{
console.log(packer.selected);
}
}]);
I've read anything I thought was relevant to my issue but I haven't managed to shed the veil of confusion. The commented code is supposed to trigger the input value change event which I hoped would update the model. I fail to understand where the model I designate in the html meets my directive's data.
https://bootstrap-datepicker.readthedocs.org/en/latest/index.html this is the plugin I'm working with.
you can pass as an attribute the model you want to modify from the controller's scope like this (look at the scope property):
Application.directive('rangepicker', function() {
return {
restrict: 'A',
scope : {
model : '='
}
link: function(scope, element, attrs, ngModel) {
$(element).datepicker({
format: 'yyyy-mm-dd'
});
$(element).on('changeDate', function(){
/*
$(element).find('input').each(function(){
$(this).trigger('input');
}) */
})
}
};
});
and in the html:
<div class="input-daterange datepicker full" model="myModelVar" rangepicker ng-model="packer.selected.date">
now the myModeVar is two way data bindable. once you change it in the directive it changes in the controller's scope.
in the controller:
Application.controller('PackingStatisticsController', ['$scope', '$http', 'initData', function($scope, $http, initData) {
$scope.myModelVar = ...;
}]);
Alas, i have done it!
Application.directive('rangepicker', function() {
return {
restrict: 'A',
require: 'ngModel', // added the ngmodel requirement
scope : {
ngModel: "="
}, // also added this, which if i understood well
// makes the 2 way data binding possible
link: function(scope, element, attrs) {
$(element).datepicker({
format: 'yyyy-mm-dd'
});
$(element).on('changeDate', function(){
var values = $(element).find('input');
var interval = {
start : values[0].value,
end : values[1].value
}
scope.$apply(function(){
// and here i update the given model scope (packer.selected.data)
scope.ngModel = interval;
});
})
}
};
});

KendoUI not working in AngularJS directive with transclude = true

In this example, I have two AngularJS KendoDatePickers. The first one works perfectly, if you click on the button you open the calendar. The second one is within a directive that has the transclude attribute set to true. If you click on the second button, you get an error.
My understanding is that the scope of the transcluded portion inherits from the control scope, so this should work. Where am I wrong?
This is the plunk
HTML
<input kendo-date-picker="picker" />
<button ng-click="openPicker()">Open Date Picker</button>
<my-window>
<input kendo-date-picker="picker2" />
<button ng-click="openPicker2()">Open Date Picker 2</button>
</my-window>
Javascript
var app = angular.module("app", [ "kendo.directives" ]);
app.controller('MyCtrl', function($scope) {
$scope.openPicker = function () {
$scope.picker.open();
};
$scope.openPicker2 = function () {
$scope.picker2.open();
};
});
app.directive('myWindow', function() {
return {
transclude: true,
scope: {
someVar: '='
},
restrict: 'EA',
template: function(element, attrs) {
var html = '<div ng-transclude></div>';
return html;
}
};
});
There are two things about your code:
first: you create an isolatedScope so you do not have access to the controller scope inside the directive scope.
second: transcluded content get their own scope. One way to work around this is by not using transclude at all, like the example below:
return {
transclude: false,
restrict: 'EA',
template: function(element, attrs) {
var html = '<div>'+element.html()+'</div>';
return html;
}
or use the link function and manually transclude the element with the scope of the directive

AngularJS: How to implement a directive that outputs its markup?

DEMO
Imagine I have some markup, e.g.:
<my-input model="data.firstName"></my-input>
Now, I would like to create a my-markup directive that will add a button to show/hide its markup.
So, this:
<div my-markup>
<my-input model="data.firstName"></my-input>
</div>
should result in this:
and when the button is clicked, the markup should appear:
The my-markup directive should not break any data bindings of its children.
Here is my attempt to implement this.
The markup appears, but the button doesn't work. Any ideas how to fix this?
PLAYGROUND HERE
Here is my approach. Couple of things:-
1) Instead of isolated scope on myMarkup, create a child scope, ultimately the actual directive myInput will be isolated. This would be required if you do need to support multiple myMarkup directive under the same scope.
2) You need a click event on the button, i wouldn't do logic on the markup instead abstract out to a method on the scope.
3) You would just need one button, do not need 2 buttons. Just change the text of the button.
.directive('myMarkup', function($compile) {
return {
restrict: 'A',
scope: true, //Create a child scope
compile: function(element) {
//Just need one button
var showButton = '<button ng-click="toggleMarkup()">{{model.showMarkup ? "Hide": "Show"}} Markup</button>';
var markup = '<pre ng-show="model.showMarkup">' + escapeHtml(element.html()) + '</pre>';
//append the markups
element.append(showButton).append(markup);
return linker;
}
};
function linker(scope, element) {
scope.model = {
showMarkup: false
};
//Click event handler on the button to toggle markup
scope.toggleMarkup = function(){
scope.model.showMarkup = !scope.model.showMarkup;
}
};
});
Demo
Please see below
function escapeHtml(html) {
return html.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
angular.module('App', []).controller('AppCtrl', function($scope) {
$scope.data = {
firstName: 'David'
};
}).directive('myInput', function() {
return {
restrict: 'E',
scope: {
model: '='
},
template: '<input class="my-input" type="text" ng-model="model">'
};
}).directive('myMarkup', function() {
return {
restrict: 'A',
scope: {},
link: function(scope, elem, attr) {
},
compile: function(element) {
var showButton = '<button ng-if="data.showMarkup" ng-click="data.showMarkup=!data.showMarkup">Hide Markup</button>';
var hideButton = '<button ng-if="!data.showMarkup" ng-click="data.showMarkup=!data.showMarkup">Show Markup</button>';
var markup = '<pre ng-if="data.showMarkup">' + escapeHtml(element.html()) + '</pre>';
element.append(showButton);
element.append(hideButton);
element.append(markup);
return function(scope, element) {
scope.data = {
showMarkup: true
};
};
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="App" ng-controller="AppCtrl">
<pre>data = {{ data | json }}</pre>
<div my-markup>
<my-input model="data.firstName"></my-input>
</div>
</body>

Resources