Let's say I want to have a directive that checks permission.
So that I coud do this:
<a permissions="something.delete">Delete</a>
If "something.delete" in the list of allowed permissions then nothing is rendered.
Having this code:
link: function (scope, element, attrs) {
var permissionsPromise = PermissionService.checkForPermissions(attrs.permissions);
permissionsPromise.then(function(result) {
if (result=== false) {
element.remove();
}
})
}
But because PermissionService.checkForPermissions() returns promise, so it may take some time to figure out the permissions, meanwhile link function will render the a-element before knowing the result of permission-check.
What would be the proper solution to fix that issue?
Reverse your logic and hide the element by default and then show it based on the promise value.
something along the lines of elem[0].style.display = 'none' -> elem[0].style.display = 'block'
Ask yourself what is your real goal, as you aren't really implementing permissions checking on the client-side (or you shouldn't be). This should be more to provide the user with the correct UI experience. If you want to remove it altogether then just keep a reference to the parent element, remove the element, and when your promise returns append it back to the parent .. or not.
Related
I have a page in which I have a few elements. My scenario is, when I double click on the background i.e., not clicking on any of the elements. I need to go 1 page back using angular. I am pretty new to angular. I am trying to figure out a way to implement. Any suggestions/help is appreciated.
Javascript has a native dblclick event, which is described here.
You'll need to check that the event is coming from the body itself, instead of one of the elements generating an event that then bubbles up to the body.
Try something like this, passing the body as the element:
link: function(scope, element, attrs) {
element.on('dblclick', function(e) {
if(e.target === element){
$window.history.back();
}
});
}
I created a nested directive structure with 2-way-object-databinding.
Everything worked like a charm on js side, until I connected it to my services which send the data to my api.
Because in the post request the object has old values, it always ignores the latest change. But the real problem is... outputting the object right before I pass it to the request it has the correct updated values.
service.updatePerson = function(person) {
console.log(person); //this outputs the correct up to date data
$http.post(Routing.generate('update_person'), person); // this works not
//After trying some more (see update below) - this works as well
$timeout(function() {
$http.post(Routing.generate('update_person'), person);
}, 1000);
};
(I am using the fos routing bundle for symfony)
Inspecting the request with my developer tools I can see that the submitted object has old values. But its not like the values never change, its always the last but not the current change.
I'm quite new to angular and might overlook something special about the $http service - I did not enable cache, so that should not be the problem.
Also the problem occurs only with the updated objects, not if I am sending a complete new entity.
The only difference I can see and I am suspecting to cause the issue is the depth of the nesting.
I have a directive called 'collapsedTable' which takes different data. Not just the data also the update, add and delete method are passed to the directive.
This is the usage of the directive:
<collapsed-table ng-if="formModel" ng-if="persons" view-data="persons" form-model="formModel" add-object="newPerson" add-item="addPerson(item)" update-item="updatePerson(item)" delete-item="deletePerson(item)"></collapsed-table>
Adding and deleting items happens directly in the directive:
<button href="#" class="btn btn-success pull-right" ng-click="addItem({item : newItem})">
Add
</button>
(delete button looks the same, but calls the delete function)
But updating data does not happen via one button, it is bound directly to the form fields and the form fields are its own directive:
<form-field ng-if="column.type" form-field-bind="item[column.name]" form-field-action="updateItem({item:item})" form-field-action-param="item" form-select-options="column.data" form-template="{{column.type}}"></form-field>
(I know, generic shit going on here)
And within the directive (editableTextarea for an example):
{{formFieldBind || 'eintragen'}}
(using the x-editable module for angular here)
I think posting the whole content of my directives is going too far. But I think the scope settings are relevant to unterstand how functions and variables are passed.
collapsedTableDirective.js
.directive('collapsedTable',['$filter',function($filter) {
return {
restrict: 'E',
templateUrl: 'templates/collapsedTableView.html',
scope: {
data: "=viewData",
newItem: '=addObject',
formModel: '=',
deleteItem: '&',
updateItem: '&',
addItem: '&'
}
}
}]);
formFieldDirective.js
.directive('formField',['formTemplateFactory', '$filter',function(formTemplateFactory, $filter) {
return {
restrict: 'E',
scope: {
formFieldBind: '=',
formFieldActionParam: '=',
formFieldAction: '&',
formTemplate: '#',
formSelectOptions: '='
},
transclude: true,
template: '<div ng-include="getTemplate()"></div>'
}
}]);
I'm also showing that form field templates are pulled via ng-include, which creates a new scope and which is the reason why the bound var is referenced with "parent" inside the form field template.
But with all questions that can be possibly discussed here, do not forget:
console.log outputs the correct data within the service. Only inside the request we have old data. Thats also the reason ist kinda difficult to debug... from the debugger side my objects always look fine.
Any help appreciated!
UPDATE - with $timeout it works
After trying some stuff out, I've got the idea to try a timeout (from a completely unrelated post). And yeah, a timeout does the trick.
But I am not answering the question because I really do not have an explanation for this behaviour. The console.log does not need a tiemout, though it should be executed before the post request.
Also there is probably a more apropriate solution than using a timeout.
person is not updated at the time when service.updatePerson runs, and it looks like it is triggered by non-Angular code which isn't adapted to digest cycles. In this case this
$scope.$apply();
// or $rootScope.$apply() if this is a service and not a controller
$http.post(Routing.generate('update_person'), person);
may help, though doing $apply in caller function and not callee is always a better habit, so external JS should trigger a wrapper instead:
scope.externalAction = function (obj) {
scope.$apply(function () {
scope.formFieldAction(obj);
};
};
console.log doesn't output the data that was actual at that moment because it was supplied with object reference, and it may reflect the changes that took place after console.log was called. This is a useful feature if taken into account, otherwise it's harmful.
To log the current object state always use
console.log(JSON.stringify(person));
I am building SPA application and I want to get advice about best practice (organize project structure) for follow issue:
My HOME page have some information and place for authentication or greeting user. In this place I need to show one of two states: first - user not is authorized and I show him form to login, second: - user is authorized and I show him "Hello, man".
I know two bad decision for this. 1. I can use ng-switch (and I think that it's not good). 2. I can use something like ng-include wich will call function for get actual html subview. (I think that it worse then first).
Also I listened about "ui router", but I'am not sure that it best way.
How will better for organize my project and what will better to use?
You can use ng-switch or ng-if controlled by a variable isLogged
I would make a template login.html and inside I would use ng-switch.
But a more advanced option is to use a directive controlled by events like:
.directive('loginDialog', function (AUTH_EVENTS) {
return {
restrict: 'A',
template: '<div ng-if="visible"
ng-include="\'login-form.html\'">',
link: function (scope) {
var showDialog = function () {
scope.visible = true;
};
scope.visible = false;
scope.$on(AUTH_EVENTS.notAuthenticated, showDialog);
scope.$on(AUTH_EVENTS.sessionTimeout, showDialog)
}
};
})
I use events in that case because this funcionality is extended to the whole application and it's very easy to control the component sending events from any place I need.
Trying to create a possibility for admins to change translations on
the current page without editing language files directly.
I'm using angular-translate and I want to catch all translated texts on a current page before interpolation. What I need:
translationId: original-value-without-interpolation
First I changed the translate filter and wrote a custom interpolator to return an object instead of just the interpolated value.
interpolate: function (string, interpolateParams) {
return {
string: string,
interpolateParams: interpolateParams,
translated: $translateDefaultInterpolation.interpolate(string, interpolateParams)
};
}
Then inside the translate filter I was able to access both the translationId and original-value.
Soon after that I remembered that I had forgotten everything about directives and who knows what else I might have forgotten that use the same interpolator.
Now it feels like it would be safer to find an alternative where I can catch the translationId and the original-value without editing filters and directives.
Is there perhaps a function that I can hook myself to that I haven't found yet?
Bear in mind that I only want to catch the translations on the current page and I need to get them before {{values}} are replaced.
angular 1.2.28
angular-translate 1.5.2
Edit: Apparently I can access to the translations table $translateProvider.translations() where I can get values before interpolation. Then I would only need to track down all the translations used on the current page. (Edit2: Unable to access the $translationTable at runtime)
I found a solution.
First thing I did was to create a custom loader. Which is based on the default one but it also stores all the loaded translations into an object. Which is later passed on.
Secondly I created a translate filter and directive. Which are also pretty much based on the provided one put with an addition of:
Filter
angular.module('app')
.filter('translate', ['$parse', '$translate', 'TranslationsHandler', function ($parse, $translate, TranslationsHandler) {
var translateFilter = function (translationId, interpolateParams, interpolation) {
if ( ! angular.isObject(interpolateParams)) {
interpolateParams = $parse(interpolateParams)(this);
}
TranslationsHandler.addViewTranslation($translate.use(), translationId);
return $translate.instant(translationId, interpolateParams, interpolation);
};
// Since AngularJS 1.3, filters which are not stateless (depending at the scope)
// have to explicit define this behavior.
translateFilter.$stateful = true;
return translateFilter;
}]);
Directive
angular.module('app')
.directive('translate', ['TranslationsHandler', '$translate', function (TranslationsHandler, $translate) {
return {
link: function (scope, el, attrs) {
TranslationsHandler.addViewTranslation($translate.use(), attrs.translate);
}
};
}]);
Both of these have the addition of TranslationsHandler.addViewTranslation
Since I mentioned that I'm trying to make it possible to change translations on page the translations object looks like this:
var stuff = {
file: {}, // Data loaded from translation files
server: {}, // Already changed & saved translations but not yet merged with files
changed: {}, // Currently changed translations
current: {}, // Current view translations
merged: {} // All together in this order: file + server + changed
}
// When in translation mode, the merged array is the one passed to angular-translate as the translation table
// Also on state change I clear the current object so that I can fill that up again
// And I use $injector.get('$translate').refresh() this after every blur event on the input to change the translation. This way I am able to display the change on the page.
I am creating an app using AngularJS where a user is given a list of words he must include in a form he will submit. For example, if the list of words is "Shnur" and "Bdur" the form will be valid if the user enters: "Shnur and Bdur were walking down the street when..."
but not valid if the write "Shnur went off in search of some grub." because only one of the two required words was included. I would also like to be able to dynamically change the color of the word/phrases once the user enters it.
Currently, I have this in my controller:
function ($scope, $routeParams, $location, Global, Submissions, Phrases){
$scope.Global = Global;
$scope.phrases = _(Phrases).shuffle().slice(0, 5);
I am injecting the Phrases service and am randomly selecting a few phrases the user must incorporate into his submission. Based on my research, it seems like the next step will be to write a directive which does the custom form validation, but there seems to be so many ways to do that and I'm really having trouble getting a clear sense of the structure of the directive that will be appropriate for this specific case. (This is my first real angular app)
I'm not totally sure it's the best solution, but here is what I would have done for something like this : http://jsfiddle.net/DotDotDot/K2S4v/
As you thought, I made a directive which is kind of complicated, so I will try to explain it here :
The structure of the directive is something like :
Passing the list of word to compare in argument
Adding an input field to the template
Listening to modification in this field
Checking new words, and counting the number of matching ones
Displaying and stuff like this
.directive('validate', function(){
return {
restrict:'A',
template:see below,
replace:false,
the template looks like this for the example, but actually it could be empty, it was a lot easier to use this for the display
<p><span ng-bind-html-unsafe="theDisplayedText"></span><br/>{{count}} words corresponding</p>
Then, the important part, I used the compile function, to add the input field, and then to specify the behavior in the postLink function
Adding the field (this could be done directly in the template too, not the main point)
compile:function(elt,attr){
var inp=new angular.element('<input>')
inp.attr('type','text')
inp.attr('ng-model','theText');
inp.attr('ng-change','changed()');
elt.append(inp)
then to specify how the directive has to react, the linking function :
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
},
post: function postLink(scope, iElement, iAttrs, controller) {
scope.count=0;
var copyOfWords= new Array(iAttrs['list'])//the list to compare with
scope.changed=function(){
var alreadyIn=Array();
scope.theDisplayedText=angular.copy(scope.theText);
var sliced=scope.theText.split(" ");//we get each word
scope.count=0
for(var i=0;i<sliced.length;i++){
if(sliced[i].length>3 && copyOfWords.indexOf(sliced[i])!=-1 && alreadyIn.indexOf(sliced[i])==-1)//the word exists and isn't counted yet
{
scope.count+=1//incrementing the counter
alreadyIn.push(sliced[i])//avoiding doubles
sliced[i]="<span class='hi'>"+sliced[i]+"</span>"//display in red
}
}
scope.theDisplayedText=sliced.join(" ");
}
}
}
The function is really naive and not really optimized, this might be the part you will have to change, but it works for the example. It's quite simple, you take the whole text, split it in words, then compare them with the list of words in argument. It has some issues (it appears that indexOf() on string arrays return also a result for every substring, so if you begin to type a word, it will be counted) but it's only for the example, in your application, you may have to perform better checks, or whatever you want (it's your app :) )
I hope this will help you in your application, if you have some issues concerning directives, there are many information in this site : http://amitgharat.wordpress.com/2013/06/08/the-hitchhikers-guide-to-the-directive/
Have fun =)