Angular translation not updating placeholder of Chosen plugin within ng-include $scope - angularjs

I have an ng-view with multiple instances of the localytics (angular) Chosen plugin. I also have an ng-include with one instance of the plugin. Both rendered on the same page.
I'm using the data-placeholder attribute to render a value which is filtered through the angular-translate plugin.
Initially I was having issues with all Chosen instances rendering the translated text when the method to update the language was being called.
I got around this by calling $route.reload() at the end of the method (not ideal, but acceptable).
I tried:
binding the values for the translations and the translate filter inline
setting them in controllers
watching the properties on the $scope (which never
triggered)
destroying the template before reloading the route
However, the placeholder within the ng-include refuses to update without the use of a hard refresh. Calling $window.location.reload() at the end of the method allows all instances to show the correct translation, but short of this I've not been able to find a way to fix the issue.
I'm assuming it's a scoping issue. Perhaps the Chosen plugin (which is a directive) creates its own scope, then the ng-include has its own scope, as does the ng-view.
All properties that are being translated, outside of the Chosen plugins, are working as expected.
Currently the angular-translate objects look like this:
var translationEN = {
SEARCH: {
'SEARCH-BTN': 'Search'
}
}
So I'm binding them inline as per the following:
<div ng-bind="'SEARCH.SEARCH-BTN' | translate">
I've also attempted some of the methods on $translate, such as $translate.refresh() to no avail.
If anyone has any ideas, any help and / or comments are very much appreciated.
Thanks in advance.

You can use the chosen attribute to pass in some configurations instead of using the data-placeholder attribute, like this:
<select chosen="{'placeholder_text_single': 'Select the options'}"></select>
Or you can write custom attributes that the chosen directive will also accept as configurations. However, when using attributes, the directive will evaluate the expression instead of using the literal value, which won't work as expected for translation purposes, as '{{ ... }}' is not a valid expression. The attributes would be like this:
<select chosen placeholder-text-single="'Select the options'"></select>

A similar problem occurred for me when the options were loaded by a promise.
With just an empty array, the translation worked fine, but putting the promise back in the code caused this behaviour.
A quick debugging in the chosen directive showed that, the element, from which the chosen angular plugin takes the template for the chosen widget, is not linked (or compiled... I'm really new with angular), it still contains the {{placeholder.string | translate}} value for the data-placeholder attribute, however, the attr.placeholder contains the tranlated value.
So this line sets wrong value as the default text: https://github.com/localytics/angular-chosen/blob/master/chosen.js#L57
I extended the chosen directive with a preLink function, which modified the element's data-placeholder attribute with the right value:
angular.module('myModule').directive('chosen', function() {
return {
priority : 1,
restrict: 'A',
link : {
pre : function(scope, element, attr, ngModel) {
var defaultText = attr.placeholder;
angular.element(element[0]).attr('data-placeholder', defaultText);
}
}
}
});

Related

Angular Directive not executing on UI Bootstrap Modal open

I have a one-page site that I am building out and this is my first time using Angular on a site. Building it on top of Laravel too for the backend but that is beyond the scope of this question.
I need to be able to open a modal on a main page view which will add a new resource (e.g. a new client) or edit a resource. I want to somehow get the form's html inside the modal body when the $uibModal.open()'s controller is called and set the $scope.modalBody equal to the injected items.modalBody (the only way this works is if I use:
$scope.modalBody = $sce.trustAsHtml(items.modalBody);
The only problem now is that anything inside the HTML body, Angular will not use it's magic and do any data-binding. It is still in the raw form of
{{ object.property }} or since I'm using Laravel and avoiding conflict with the Blade template engine:
<% object.property %>
See screenshot:
screenshot
I have been banging my head against the wall on this one...I have tried putting $scope.$apply() in my directive and my controller, neither of which worked. I have a feeling that is the source of my problem though. I have also tried making the html just a <new-client></new-client> directive and using templateUrl: 'views/clients/add.php' which would be ideal, but the template is not being included inside the <new-client></new-client>.
I'm using ui-bootstrap 0.14.3 and Angular 1.4.8.
Could this be a bug? Or am I doing something wrong? Anyone have a better way of getting a form into my modal? Let me know what code you want to see so I don't clutter this post with unnecessary code blocks.
I have come across a similar issue with using jQuery's AJAX to receive template strings and append it to a server.
So when HTML is added via jQuery, bound html string, etc., angular doesn't know it needs to automagically compile this data.
What you need to do is use the $compile service, to $compile your html and then attach the correct $scope to it:
`$compile('jQuerySelectorReturningHtmlOrAnHTMLStringThatNeedsToBeCompiled')($scope);`
There are multiple examples in Angulars Documentation for $compile that can give you an idea of what is happening. I think by what you have described the same thing is happening here in your situation.
The key is to call this $compile service function after the html has been bound to the page.
EDIT:
There are a few other options based on some comments, that will serve as a viable solution to rendering this content on your view. For example a directive that takes a string attribute representing the HTML string of your desired view.
1. Modify your directive template in the compile step:
You have the ability to modify your template before the directive compiles and binds any attributes to it, to that directives scope:
app.directive('myAwesomeCompileStepDirective', [myAwesomeCompileStepDirectivef]);
function myAwesomeCompileStepDirectiveFn() {
return {
restrict: 'EA',
compile: function compileFn(tAttrs, tElement) {
//Here you can access the attrs that are passed into your directive (aka html string)
tElement.html(tAttrs['stringThatYouWantToReplaceElementWith']);
return function linkFn(scope, element, attrs, controller, transcludeFn) {
//if all you want to do is update the template you really don't have to do anything
//here but I leave it defined anyways.
}
}
}
}
You can view a file I wrote for a npm component which uses this method to modify my directive template before it is compiled on the page & you can also view the codepen for the complete component to see it in action.
2. Use $compile service to call $compile in link function using directive attrs.
In the same way as the aforementioned method, you can instead inject the $compile service, and call the function mentioned above. This provides a bit more work, for you but more flexibility to listen to events and perform scope based functions which is not available in the compile function in option 1.

Directive implementation advice using attribute string binding

I am new to AngularJS and directives and am seeking some advice or guidance on a directive I have implemented. The directive in question will be used to display pdf's to the user. The directive exposes two attributes, documentPath and documentType that are defined using isolated scope as follows:
var directiveDefinition = {
templateUrl: 'app/views/directives/document.html',
scope: {
documentPath: '#documentPath',
documentType: '#documentType'
},
restrict: 'E',
templateNamespace: 'html',
link: linkFunc
};
In the view that uses the directive, I bind the properties using a model property for the view controller and a string.
<my-document document-path="{{ application.documentpath }}" document-type="Application"></my-document>
When I initially ran this, I found that the directive would sometimes run before the data had been returned by the model. So an empty document would be displayed. Other times, the model would load before the directive ran, so the document path would be present when the link function ran, allowing the document to be displayed.
I determined that one way to resolve this was to use a $watch listener on the documentPath attribute of the directive. This seems to resolve the issue.
Being new to AngularJS and also directive implementation my question is...was this the best solution? Any advice would be greatly appreciated. Thanks!
The $watch solution should work fine but depending on the complexity of your app and the number of total watchers this could lead to a slow $digest cycle.
Another option would be to send the parameters as a reference using the "." dot notation so you it will get updated when the data gets loaded from model (from the API most of the time since this is the slow part) instead of sending a string primitive.
Your directive scope declaration would become:
scope: {
documentPath: '=',
documentType: '='
},
and you will use it from the parent view like this:
<my-document document-path="application.documentpath" document-type="application.documenttype"></my-document>
If the documentType is always the same you may leave it as a string scope parameter.

Angular directive doesn't work for elements added to the DOM by jquery plugin

I'm creating a fall back image directive that looks like this http://plnkr.co/edit/wxy4Sp2K02iXoQNsvkah
angular.module('directives').directive('myDirective', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
console.log('linking');
}
}
});
My directive doesn't work for elements that are added to the DOM by the typeahead.js plugin (https://github.com/twitter/typeahead.js).
<div class='tt-suggestion'>
<div><span class="my-directive">bla</span></div>
</div>
I guess it's because Angular is not informed about the elements that are added by jQuery and hence it doesn't invoke the directive. How do I notify Angular of these changes?
You can use the Angular compile service to do this: http://docs.angularjs.org/api/ng/service/$compile
Basicly it works like this:
document.getElementById("test").innerHTML = $compile("")($scope);
ideally you shouldnt be mixing jquery and angular because they both are based on different philosophy.
jquery-- is event driven i.e. have event listeners which cause changes to model and then the programmer has to code numerous lines to change the view i.e. changing css,text etc
angular-- woo hoo! just change the model which is binded to $scope and :) your view is automatically updated
to automatically react on changing of such events angular has a compiler which studies entire html code before the app is loaded so even if there is a template which you might use later you must enclose it in so that angular compiles this so that all the special angular directive and controller perform as expected even when you remove or add templates to the dom.
here you are using typehead.js and jquery to manually manipulate the view which is against angular philosophy because when you do such maipulation angular compiler wouldnt be aware of it as it runs only when the app is initialized. Thats why before appending you should use $compile to make the angular compiler aware of this template .
in your case i would suggest the typehead present on this url
http://angular-ui.github.io/bootstrap/

Two way databinding not working on directive

I have a directive and I want to change the ng-model value given with this directive...
I'm setting scope: {ngModel: '='} and I'm changing the ngModel value (on click event) inside my directive but I can't see changes on my external/original object.
This plunker shows the problem...
There are a few things wrong here, all of them common mistakes.
Event handlers registered through jQuery using $(...).on(...) will be executed outside of angular context, so angular will not know when things have updated. To address this, you must wrap the contents in a scope.$apply call like so
$('#aaa').on('click', function() {
_scope.$apply(function(){
_scope.ngModel = 'Other Value';
updateTemplate();
});
});
This will update the binding to the input with ng-model. In fact you can avoid having to do this by using the ng-click directive.
With angular, you do not need to update templates like this yourself using .html(...). Binding is one of the major features of the framework. Instead of having the update function, you can use interpolation by putting an expression inside of {{ ... }} and your DOM will be updated when your model is. For example when defining the directive you can use
template: '<div id="aaa">{{ngModel}}</div>'
to set your template and {{ngModel}} will show the current value of ngModel.
ngModel is not just any attribute, it is a powerful directive. If you need your own directive to be able to declare the current model valid or invalid, or to interact with forms then you should use this through the require property on your controller (see here).
If you don't need those features then you should be calling your attribute something different to avoid conflict.
I have updated the plunker to include these points.

Creating an AngularJS Directive for jQuery UI Button

Update: Fiddle w/ full solution: http://jsfiddle.net/langdonx/VXBHG/
In efforts to compare and contrast KnockoutJS and AngularJS, I ran through the KnockoutJS interactive tutorial, and after each section, I'd rewrite it in AngularJS using what little I already knew + the AngularJS reference.
When I got to step 3 of the Creating custom bindings tutorial, I figured it would be a good time to get spun up on Angular Directives and write a custom tag. Then I failed miserably.
I'm up against two issues that I haven't been able to figure out. I created a new Fiddle to try and wrap my head around what was going on...
1 (fiddle): I figured out my scoping issue, but, is it possible to just passthrough ng-click? The only way I could get it to work is to rename it to jqb-click which is a little annoying.
2 (fiddle): As soon as I applied .button() to my element, things went weird. My guess is because both Angular and jQuery UI are manipulating the HTML. I wouldn't expect this, but Angular seems to be providing its own span for my button (see line 21 of the JavaScript), and of course so is jQuery UI, which I would expect. I hacked up the HTML to get it looking right, but even before that, none of the functionality works. I still have the scope issue, and there's no template binding. What am I missing?
I understand that there's an AngularUI project I should be taking a look at and I can probably pull off what I'm trying to do with just CSS, but at this point it's more about learning how to use Directives rather than thinking this is a good idea.
You can create an isolated scope in a directive by setting the scope parameter, or let it use the parent scope by not setting it.
Since you want the ng-click from parent scope it is likely easiest for this instance to use the parent scope within directive:
One trick is to use $timeout within a directive before maniplulatig the DOM within a templated directive to give the DOM time to repaint before the manipulation, otherwise it seems that the elements don't exist in time.
I used an attribute to pass the text in, rather than worrying about transclusion compiling. In this manner the expression will already have been compiled when the template is added and the link callback provides easy access to the attributes.
<jqbutton ng-click="test(3)" text="{{title}} 3"></jqbutton>
angular.module('Components', [])
.directive('jqbutton', function ($timeout) {
return {
restrict: 'E', // says that this directive is only for html elements
replace: true,
template: '<button></button>',
link: function (scope, element, attrs) {
// turn the button into a jQuery button
$timeout(function () {
/* set text from attribute of custom tag*/
element.text(attrs.text).button();
}, 10);/* very slight delay, even using "0" works*/
}
};
});
Demo: http://jsfiddle.net/gWjXc/8/
Directives are very powerful, but also have a bit of a learning curve. Also in comparison of angular to knockout, angular is more of a meta framework that in the long run has far more flexibilty than knockout
Very helpful reading for understanding scope in directives:
https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance

Resources