creating angular-js directive that updates ng-model - angularjs

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.

Related

Angular UI Typeahead custom directive - model not updated on select

I made custom directive that use Angular UI typeahead directive inside, but not working as expected. In my directive model is not updating on select. Anybody helps with that ? For testing i used static array instead of http service. Plunker HERE.
.directive('httpDictionary', ['$compile', function($compile){
return {
scope: {
},
restrict: 'A',
controllerAs: "dm",
controller: ['$scope', '$http', 'ARRAY', function($scope, $http, ARRAY){
var dm = this;
dm.dict = function(val){
return ARRAY; // for testing only
// return $http.get($scope.dictionaryUrl, { ...
}
}],
link: function(scope, element, attributes, ngModel) {
scope.dictionaryUrl = attributes.httpDictionary;
element.removeAttr('http-dictionary'); // avoid loop
element.attr('uib-typeahead', 'd for d in dm.dict($viewValue)');
$compile(element)(scope);
}
};
}])
To help with binding to a model, I usually use uib-typeahead setting typeahead-on-select. That way, you can check against or execute some additional code when the new model is set.
I made a plunker off your code found here but made some small tweaks:
I isolated the input text field into its own view so as to separate the functionality from the parent view.
I passed the test model to the directive's scope attribute and then bound it to the directives controller (using bindToController attribute) so if you ever needed the test model to communicate with the parent controller in the future, you could do so.
Hopes this helps.
I made this works like below. Plunker code here. I've bind ngModel in directive scope and used typeahead-on-select callback function. I don't think so this is elegant, but works. I've been thinking about using $watch, but without success. If You have a better soultion, i'll be glad.
.directive('httpDictionary', ['$compile', function($compile){
return {
scope: {
ngModel: '='
},
restrict: 'A',
controllerAs: "dm",
controller: ['$scope', '$http', 'ARRAY', function($scope, $http, ARRAY){
var dm = this;
dm.dict = function(val){
return ARRAY; // for testing only
// return $http.get($scope.dictionaryUrl, { ...
}
dm.select = function($model) {
$scope.ngModel = $model;
}
}],
link: function(scope, element, attributes, ngModel) {
scope.dictionaryUrl = attributes.httpDictionary;
element.removeAttr('http-dictionary'); // avoid loop
element.attr('uib-typeahead', 'd for d in dm.dict($viewValue)');
element.attr('typeahead-on-select','dm.select($model)');
element = $compile(element)(scope);
}
};
}])

Angular directive model binding

I have just discovered AngularJS and am in what seems to be a fairly steep learning curve. Can anyone recommend a couple of good books that will take a "practical" dive into AngularJS. My programming question is this:
Consider:
<input type="text" name="inputField1" ng-model="myModel.firstName"
encrypt-and-save="myModel.encryptedFirstName" />
In my directive named "encryptAndSave" I want to dynamically bind to the model property whose name (in this case) is "encryptedFirstName". Everything that I have read seems to say that this is possible, but I haven't found a concrete example of how it is done. Any help/pointers would be greatly appreciated.
Thanks in advance,
Jimmy
Here's what I wound up doing. I discovered $parse and .assign. I used the $parse at initialization and .assign for late/real-time binding. Does this make sense, or have I totally missed something?
app.directive('encryptAndSave', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
var encryptedModelValue = $parse(attrs.encryptAndSave);
//
// wait for model change (could also wait for blur??)
//
scope.$watch(attrs.ngModel, function(newValue, oldValue) {
var encrValue = encryptThis(newValue);
encryptedModelValue.assign(scope, encrValue);
});
}
};
});
Thanks again for your help,
Jimmy
In you directive create isolated scope and use 2 way binding via '='
scope: {
encryptAndSave: '='
}
Please see demo below
var app = angular.module('app', []);
app.controller('firstCtrl', function($scope) {
$scope.myModel = {
firstName: "joe",
encryptedFirstName: " encrypted joe"
};
});
app.directive('encryptAndSave', function() {
return {
scope: {
encryptAndSave: '='
},
link: function(scope, elem, attr) {
alert(scope.encryptAndSave)
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="firstCtrl">
<input type="text" name="inputField1" ng-model="myModel.firstName" encrypt-and-save="myModel.encryptedFirstName" />
</div>
</body>
I'm all about the watchers for this one. The reason is that you can't access dynamic members of the parent scope using an isolated scope. I may be wrong, but it looks like this is what you're trying to do.
angular.module("encryptMe", [])
.directive("encryptAndSave", function() {
function hash(name) {
return name;
}
return {
link: function(scope, element, attrs) {
scope.$watch("firstName", function() {
scope[attrs.encryptAndSave] = hash(scope.firstName + "");
});
}
};
})
.controller("encryptController", function($scope) {
$scope.firstName = "Who?";
});
Naturally, you would want to more interesting stuff in your hash function. The main thing here is that this directive pays attention to the value passed in when declaring the directive (encrypt-and-save=[your variable here]). It then watches the firstName variable and updates the given parent-scope variable with the hash of the new value.
Feel free to try it in a fiddle.

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

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

How can I change information in another view via an AngularJS Directive?

Okay, this is a convoluted question and I may be asking for a bad approach. If that's the case, please do let me know.
So I have a directive for a navigation bar. When someone clicks something, I managed to get the directive to add a class and therefore load the bar. Thanks to StackOverflow.
But now, I have a service that gets and sets values. When a value is changed in the service, I want to reflect that in a view. Is such a thing possible?
EDIT
For clarification, if I do use a $apply(function()...., how exactly do I do that? My view has something like. My view is not bound to any particular controller, or scope. Not sure if it should be. But here's a snippet of my view:
<p>
Are you sure you change that song,
<br />
{{ songs[0].title }}
</p>
Here's my directive:
angular.module('MyApp')
.directive('navbar', function () {
return {
restrict: 'A',
templateUrl : '/views/partials/nav.html',
controller: function ($scope, ModalService) {
$scope.ms = ModalService;
$scope.songs = {};
$scope.$watch('ms.songs', function(newVal, oldVal) {
if(newVal != null) {
$scope.$apply(function() {
$scope.songs = newVal;
});
}
});
},
Have you tried this?
angular.module('MyApp')
.directive('navbar', function () {
return {
restrict: 'A',
templateUrl : '/views/partials/nav.html',
controller: function ($scope, ModalService) {
$scope.songs = ModalService.songs;
}
});
I did run into a scenario recently where I had to setup a watch on a service property within a directive and the solution was to setup the watch within the link function similar to:
link: function(scope, element, attrs) {
// other link code
scope.$watch(function() { return svc.property; }, function(data) {
// do something here
});
// other link code
}

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!

Resources