Using angular directive to watch for change values with bootstrap-select - angularjs

I am new to angular and after having researched for 2 days, I still haven't come up with a solution that will work yet.
I have a select item that will have its options updated and am also using bootstrap-select.js. I can get either or to work on their own (angular items updating dynamically as expected in a standard select list or the bootstrap-select item to work with static options). If someone could provide some guidance as to what I am doing wrong, it would be greatly appreciated! Here is my code:
HTML:
<div ng-app="app">
<div ng-controller="ctrl">
<selectpicker data-array="users" data-selected="info.selected"></selectpicker>
<button ng-click="add()">Add</button>
</div>
</div>
JS:
var app = angular.module('app', []);
app.controller('ctrl', ['$scope', function($scope)
{
$scope.info = {selected: 1};
$scope.users=[];
$scope.users.splice(0);
$scope.users = [{name: "Bob", id: "1"},{name:"Tom", id: "2"}];
$scope.add = function () {
$scope.users.push({name: "John", id: "3"});
};
}]);
app.directive('selectpicker', function($timeout)
{
return {
restrict: 'E',
replace:true,
scope: {
selected: '=',
array: '=',
class: '='
},
template: '<select class="selectpicker" multiple data-selected-text-format="count" ng-model="currentName" ng-options="user.name for user in array">' +
'</select>',
replace:true,
link: function(scope, el, attrs) {
$timeout(function () {
scope.$watch('array', function (newVal) {
console.log(scope.array);
var select = $(el).selectpicker();
select.change(function(evt) {
var val = $(el).selectpicker('val');
$scope.selected = val;
$scope.$apply();
});
}, true);
});
}
};
});
So when I click the Add button, I can see the scope.array value
updates from the console output, but the dropdown itself won't update. I've tried piecing together solutions from similar answers but nothing has yielded results so far.

According to the documentation of Bootstrap-select, you could use refresh() method to update the UI when an underlying select tag has been changed like this:
$(el).selectpicker('refresh');
But lets look further how to improve the directive:
the element variable el is already a jQuery object, no need to wrap it again as $(el).
a $timeout is become unnecessary after the UI refresh is properly handled.
move the change event binding out of $watch, otherwise the handler will be fired multiple times per a change.
$watchCollection is enough if the options will be only add/remove i.e. an individual option will not be changed.
The final result would look like this:
link: function(scope, el, attrs) {
var select = el.selectpicker();
select.change(function (evt) {
scope.selected = el.selectpicker('val');
scope.$apply();
});
scope.$watchCollection('array', function(newVal) {
console.log(scope.array);
el.selectpicker('refresh');
});
}
Example Plunker: http://plnkr.co/edit/Kt0V0UBuHaRYMjKbI5Ov?p=preview

Related

Why isnt my ng-model updating?

I have a directive that I have created. There is a text box and I am trying to update the count of characters in the box. First of all here is the html where my directive is declared.
<div chat-container encounter="selectedEncounter" ng-model="count"></div>
Here is my directive.
angular.module('clinicalApp').directive('chatContainer', function() {
return {
scope: {
encounter: '=',
ngModel: '='
},
controller: 'EncounterCtrl',
templateUrl: 'views/chat.container.html',
link: function(scope, elem, attrs) {
var chatbox = elem.find('textarea');
chatbox.bind('keyup',function() {
scope.updateCount();
});
}
};
});
In my template is a {{count}} that I want to be updated.
In my controller I have a function that I is called in the bind that updates the count. Here is the function in my controller.
$scope.updateCount = function() {
$scope.count = 350;
};
When this function is hit in my controller it does not update the count in my directive. How do I make this work?
Even better, how do I make the directive update the count? I thought the 2-way binding would take care of this, but it isnt. Thanks for the help.
My problem was that I was trying to update the wrong attribute in the template. If you look above I was trying to update {{count}} and I needed to update {{ngModel}}.
You need to notify angular that you updated a value:
$scope.updateCount = function() {
$scope.$apply( function() {
$scope.count = 350;
});
};

AngularJS scope updated after ng-change

I have a directive that centralises my select HTML and functionality but I have an issue where the ng-model is updating after ng-change happens.
Here's a focused jsfiddle example:
http://jsfiddle.net/U3pVM/1568/
(Code because SO complains otherwise)
HTML:
<div ng-app="myApp">
<div ng-controller="MainCtrl">
<p>fooId is currently : {{fooId}}</p>
<app-drop-down model="fooId" options="fooOptions" opt-value="id" opt-label="label" on-change="dropDownChanged"></app-drop-down>
</div>
</div>
JS:
var app = angular.module('myApp', []);
app.controller('MainCtrl', function ($scope, $log) {
$scope.fooId = -1;
$scope.fooOptions = [{
id: 1,
label: "A"
}, {
id: 2,
label: "B"
}];
$scope.dropDownChanged = function (id) {
$log.info('changed : ' + $scope.fooId + ' but really: ' + id);
};
});
app.directive('appDropDown', function () {
return {
restrict: 'E',
replace: true,
scope: {
model: '=',
options: '=',
onChange: '='
},
template:
'<div><select ng-model="model" ng-options="a[optValue] as a[optLabel] for a in options" ng-change="changed()"></select></div>',
link: function (scope, element, attrs) {
scope.optValue = attrs.optValue;
scope.optLabel = attrs.optLabel;
scope.changed = function () {
scope.onChange(scope.model);
};
}
};
});
The console logs:
changed : -1 but really: 1
changed : 1 but really: 2
When you change the select to A, then to B.
It is updating but after the ng-change is triggered.
Obviously, I can work around this by passing the id (like I do) or using $watch in the controller on the value but this isn't ideal for certain more complex scenarios.
Any ideas?
I know this is a bit after the fact, but I had a similar problem and searching around I found this question as well. As there doesn't seem to be a real answer, I thought to post what I ended up doing as it may help someone else in the future. This seems to work for my case (and your fiddle as well) but as I'm only starting to use AngularJS, I might be doing something against the 'rules' so any specialist, feel free to correct me...
Anyway, here is an updated version of your Fiddle with my changes:
http://jsfiddle.net/5vb5oL7e/1/
And here is the actual code of the directive:
app.directive('appDropDown', function () {
return {
restrict: 'E',
replace: true,
scope: {
model: '=',
options: '=',
onChange: '='
},
template:
'<div><select ng-model="model" ng-options="a[optValue] as a[optLabel] for a in options"></select></div>',
link: function (scope, element, attrs) {
scope.optValue = attrs.optValue;
scope.optLabel = attrs.optLabel;
scope.$watch('model', function(newValue, oldValue)
{
// Execute function on change
if (scope.onChange !== undefined &&
newValue !== undefined && oldValue !== undefined)
{
scope.onChange();
}
});
}
};
});
Basically, what I did was to add a watch inside the link function on the model. Inside this watch I fire the onChange function when it's defined. The added checks for undefined on the old and new value were added to prevent the function to change unneeded on page load.
Hope this helps someone...
Kind regards,
Heino

AngularJs how to call prettyprint?

I'm trying to use prettyprint plugin for my angularjs app.
But cannot make it works. I create a simple directive and call method prettyPrint(), but the code is not formatted.
FIDDLE: http://jsfiddle.net/Tropicalista/yAv4f/2/
App.directive('test', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$(element).prettyPrint()
}
};
});
I modified your code and i'll update here:
http://jsfiddle.net/yAv4f/6/
html:
<div ng-app="Knob" ng-controller="myCtrl">
<pre class="prettyprint linemus"></pre>
<pre class="prettyprint linemus"><!DOCTYPE html><html lang="en"></html></pre>
</div>
javascript:
var App = angular.module('Knob', []);
App.controller('myCtrl', function($scope) {
$scope.dom = '<!DOCTYPE html><html lang="en"></html>'
})
App.directive('prettyprint', function() {
return {
restrict: 'C',
link: function postLink(scope, element, attrs) {
element.html(prettyPrintOne(scope.dom));
}
};
});
Basically, you need to use the file prettify.js to control the execution of the prettify() function, with prettyPrintOne() you can execute it in a specific html text.
And to simplify the use of the directive, like prettify stlyle, i'll suggest restric to 'C' a class and change the the directive name to 'prettyprint'
I've expanded on the previous answers and created a jsfiddle with a working directive that responds in realtime to model changes:
http://jsfiddle.net/smithkl42/cwrgLd0L/27/
HTML:
<div ng-app="prettifyTest" ng-controller="myCtrl">
<div>
<input type="text" ng-model="organization.message" />
</div>
<prettify target="organization"><pre><code class="prettyprint">console.log('{{target.message}}');
</code>
</pre>
</prettify>
</div>
JS:
var App = angular.module('prettifyTest', []);
App.controller('myCtrl', function ($scope) {
$scope.organization = {
message: 'Hello, world!'
};
});
App.directive('prettify', ['$compile', '$timeout', function ($compile, $timeout) {
return {
restrict: 'E',
scope: {
target: '='
},
link: function (scope, element, attrs) {
var template = element.html();
var templateFn = $compile(template);
var update = function(){
$timeout(function () {
var compiled = templateFn(scope).html();
var prettified = prettyPrintOne(compiled);
element.html(prettified);
}, 0);
}
scope.$watch('target', function () {
update();
}, true);
update();
}
};
}]);
h/t to #DanielSchaffer (see Template always compiles with old scope value in directive).
Angular already has this filter built-in for JSON:
<pre>
{{data | json}}
</pre>
If you want to make your own directive, you can use the JSON object directly:
app.filter('prettyJSON', function () {
function syntaxHighlight(json) {
return JSON ? JSON.stringify(json, null, ' ') : 'your browser doesnt support JSON so cant pretty print';
}
return syntaxHighlight;
});
With markup
<pre>
{{data | prettyJSON}}
</pre>
I would like to make a small addition to the directive by #carlosmantilla
You can achieve the same thing without creating the scope variable. I have added this correction on github
This should work properly I assume.
http://jsfiddle.net/yAv4f/143/
var App = angular.module('Knob', []);
App.controller('myCtrl', function($scope) {
$scope.text = "function f1(){int a;}";
})
function replaceText(str)
{
var str1 = String(str);
return str1.replace(/\n/g,"<br/>");
}
app.directive('prettyprint', function() {
return {
restrict: 'C',
link: function postLink(scope, element, attrs) {
element.html(prettyPrintOne(replaceText(element.html()),'',true));
}
};
});
I struggled with this issue for quite a while and wanted to chime in here, albeit much later than everyone else (for real though, who's still using AngularJS in late 2017? This guy.) My specific use-case was where I have code (xml) being dynamically loaded on the page which needed to be pretty printed over and over again.
This directive will take in your code as an attribute, remove the prettyprinted class that's added to the element right after you run prettyPrint(). It will watch for changes on the inputted code from the parent's scope and run the code again when changes occur.
Only dependency is that you have Google's code-prettify. I had it self-hosted, hence the PR.prettyPrint() (as instructed in the docs as of sept 2017).
The directive fully encapsulates the needed Google code-prettify functionality for dynamic content.
angular.module('acrSelect.portal.directives')
.directive('prettyPrint', ['$timeout', function($timeout) {
return {
restrict: 'E',
scope: {
'code': '=',
},
template: '<pre ng-class="{prettyprint: code}">{{ code }}</pre>',
link: function (scope, element, attr) {
scope.$watch('code',function(){
$timeout(function() {
//DOM has finished rendering
PR.prettyPrint();
element.find(".prettyprint").removeClass("prettyprinted");
});
});
}
}
}
]);
The html in the parent template might look
<pretty-print code="selectedCode" ng-show="codeIsSelected"></pretty-print>
Hope this helps another poor soul!

creating angular-js directive that updates ng-model

I am trying to create a directive that wraps the twitter typeahead plugin. What I have so far is:
HTML:
<input ng-twitter-typeahead type="text" ng-model="participant" data="exampleData" />
{{ participant }}
I want the value for 'participant' to be updated when I select something on the typeahead. The typeahead itself works properly, but I can't capture the selected value. Below is the javascript:
var app = angular.module('myApp', [])
app.directive('ngTwitterTypeahead', function () {
return {
restrict: 'EA',
scope: {
data: '='
},
link: function ($scope, $element, $attrs) {
$element.typeahead($scope.data);
$element.bind('typeahead:selected', function(obj, datum) {
// I really don't know how to do this part
// the variable 'datum' is what I want to be passed to ng-model
// I tried things like:
// Including the ngModelController and typing:
// ngModel.$setViewValue(datum)
// but that didn't work.
}
};
});
I'm obviously missing something fundamental when it comes to AngularJS. Any help would be greatly appreciated!
EDIT **
I found the solution. I am clueless sometimes:
angular.module('siyfion.ngTypeahead', [])
.directive('ngTypeahead', function () {
return {
restrict: 'C',
scope: {
datasets: '=',
ngModel: '='
},
link: function ($scope, $element, $attrs) {
$element.typeahead($scope.datasets);
$element.bind('typeahead:selected', function(obj, datum) {
$scope.$apply(function() {
$scope.ngModel = datum;
});
})
}
};
});
You could require ngModel controller inside the directive. It will give you an access to the model controller inside the link function, see http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController
Here you can find an example how to use it the real life http://suhairhassan.com/2013/05/01/getting-started-with-angularjs-directive.html#.UhSdDOZdXUE
The twitter Typeahead project seems to be abandoned.
But you can use the Typeahead Angular directive from the Angular UI Bootstrap library that is being actively maintained as of now.

AngualarJS Directives - Waiting for a directive template to finish rendering?

I'm trying to write a directive that has a template. The template is rendering some DOM elements I want to retrieve. However, when I try to retrieve my DOM elements in the linking function, the DOM elements are not found. If I add a window.setTimeout method before selecting the elements they are found. How can I wait for a template to finish rendering before trying to manipulate the DOM in the linking function?
Here is the directive code for what I'm trying to do:
module.directive('testLocationPicker', function() {
var linkFn = function(scope, element, attrs) {
console.log('in linking function');
window.setTimeout(function() {
var positions = $('.position');
console.log('number positions found: ' + positions.length);
positions.click(function(e) {
console.log('position clicked');
scope.$apply(function() {
scope.selectedPosition = $(e.currentTarget).html();
});
});
}, 500);
};
return {
link: linkFn,
restrict: 'E',
template: 'Choose a position: <div class="position" ng-repeat="position in positions">{{position}}</div>',
}
});
I have a JS Fiddle of what I'm trying to do: http://jsfiddle.net/bdicasa/XSFpu/42/
I would recommend doing something like this instead:
var module = angular.module('test', []);
module.controller('TestController', function($scope) {
$scope.positions = [
'Test Position 1',
'Test Position 2',
'Test Position 3'
];
$scope.selectedPosition = '';
$scope.handleClick = function (index) {
$scope.selectedPosition = $scope.positions[index];
}
});
module.directive('testLocationPicker', function() {
return {
restrict: 'E',
template: 'Choose a position: <div class="position" ng-repeat="position in positions" ng-click="handleClick($index)">{{position}}</div>',
}
});
Instead of trying to search through the dom and add a click event, just modify your template like this:
template: 'Choose a position: <div class="position" ng-repeat="position in positions" data-ng-click="positionClick($index)">{{position}}</div>',
And then create a positionClick function in the linking function:
var linkFn = function(scope, element, attrs) {
scope.positionClick = function(index){
scope.selectedPosition = index;
}
};
Working jsFiddle: http://jsfiddle.net/XSFpu/77/
The reason your method is not working is because the ng-repeat hasn't fired after the template has loaded. So it's loaded the directive in, and the link function has been hit, but the ng-repeat actually hasn't started repeating yet. This is why I'm suggesting moving some of your code around to accomdate that.

Resources