Simple directive to work like a binding expression [duplicate] - angularjs

This question already has answers here:
Does AngularJS have a bug when processing a custom directive for an HTML Void Element
(2 answers)
Closed 6 years ago.
what i am trying to do is create the following markup:
<current-user />
this directive should simply inject the current users username just like a binding expression {{currentUser.name}}
here is what i have but i am losing my span tag at the end for the caret:
HTML:
<a href="" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-user"></i>
<current-user />
<span class="caret"></span>
</a>
Javascript:
app.directive('currentUser', function ($rootScope, auth) {
return {
restrict: 'E',
transclude: true,
compile: function (elem) {
$rootScope.$watch('auth.profile', function (profile) {
if (profile) {
elem.html(profile.email);
}
});
}
}
});
any help would be greatly appreciated

This is a known issue with some browsers.
Use a non-self closing directive to fix the problem.
<a href="" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-user"></i>
<current-user></current-user>
<span class="caret">Test</span>
</a>
Working Demo

Related

Create a Dynamic Angular Directive

I have a html snippet that I'm now having to duplicate a lot which brings me to my question as I'd like to make the html snippet into a directive that can be reused.
I'd like to transform the snippet below into a directive.
<a href="#"
ng-click="vm.orderBy ='UserName'; reverseSort = !reverseSort">
User Name
<span ng-show="vm.orderBy == 'UserName'">
<span ng-show="!reverseSort"><i class="fa fa-sort-alpha-asc"></i></span>
<span ng-show="reverseSort"><i class="fa fa-sort-alpha-desc"></i></span>
</span>
</a>
What I'd like is a directive that allows me to pass any string property to the orderBy field which would make it dynamic.
so something like <my-directive sort = 'Username'></my-directive>
I have vm.orderBy = '' initialised in my main controller.
angular.module('app_name', []).directive('myDirective', myDirective);
myDirective.$inject = ["$scope"]; // dependecny injection
function myDirective($scope) {
return {
restrict: 'E',
templateUrl: 'mydir.tmpl.html',
scope: {
sort: "#"
}
}
}
mydir.tmpl.html
<a href="#" ng-click="vm.orderBy=sort; reverseSort = !reverseSort">
User Name
<span ng-show="vm.orderBy == sort">
<span ng-show="!reverseSort">
<i class="fa fa-sort-alpha-asc"></i>
</span>
<span ng-show="reverseSort">
<i class="fa fa-sort-alpha-desc"></i>
</span>
</span>
</a>
Then use
<my-directive sort = 'Username'></my-directive>
I was able to sort this after all.
.directive("myDirective", function () {
return {
scope:{prop:'#'},
templateUrl:'/templates/Sorter.html'
}
});
and the html like this.
<my-directive prop="UserName"></my-directive>

$event.stopPropagation doesn't play nice with Bootstrap data-toggle

I have a list of elements, each containing a ng-click. Nested inside each element is a div that should toggle a Bootstrap modal.
I have added the $event.stopPropagation to the nested div because i don't want the ng-click event from the parent element to fire, but this causes the modal to not begin displayed.
<div id="segment{{segment.Id}}" class="message" ng-class="{activeSegment: segment.Selected}" ng-click="!segmentIsLoaded || selectSegment(segment)">
<div>
<div class="pull-left">
<span>ID: {{segment.Id}}</span>
<span>{{segment.Name}} </span>
<i class="fa fa-info-circle fa-lg" data-toggle="tooltip" data-original-title="{{segment.Description}}" tooltip></i><br />
<small>{{formatJsonDate(segment.Updated)}}</small>
</div>
<div class="btn btn-danger pull-right" data-toggle="modal" data-target="#deleteSegmentModal" ng-click="$event.stopPropagation();">
<span><i class="fa fa-trash-o" aria-hidden="true"></i></span>
</div>
</div>
</div>
Any known work-arounds for this?
I should perhaps mention that the stopPropagation() works as far as preventing the click event from firing. My issue is with the bootstrap modal not being activated through data-toggle attribute
Have you tried using a directive
<a ng-click='expression' stop-event='click'>
.directive('stopEvent', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
if(attr && attr.stopEvent)
element.bind(attr.stopEvent, function (e) {
e.stopPropagation();
});
}
};
});
check SO link How can I make an AngularJS directive to stopPropagation?

Two-way binding on the contents of a recursive directive in Angular?

I'd like to produce a recursively structured document corresponding to a tree structure in my model.
The entire document is contentEditable and the models should react to changes made to an <li> within the view.
I'm using the RecursiveHelper module to avoid endless loops. I'm still trying to figure out post-link, compile, etc.
I'm a little confused which elements are associated with which controllers and scopes.
I know that an iscolate scope is being created at each level of recursion, but I'm not sure how that affects my ability to reference variables within that iscolate scope as models to bind to.
In my main.js:
.directive('bullet',function(RecursionHelper){
return {
restrict: "E",
scope:
{
node: '=node',
},
controller: function(),
template:
`
<button class="btn btn-default" ng-click="node.toggleExpanded()" ng-show="node.children.length != 0">
<span ng-show="!node.expanded" class="glyphicon glyphicon-plus" aria-hidden="true"></span>
<span ng-show="node.expanded" class="glyphicon glyphicon-minus" aria-hidden="true"></span>
</button>
{{node.content}}
<ul class="list-group-sm" ng-show="node.expanded">
<li class="list-group-item" ng-repeat="child in node.children">
<bullet node="child" ng-model="child"></bullet>
</li>
</ul>
`,
compile: function(element) {
return RecursionHelper.compile(element, function(scope, elm, attrs, ctrl, transcludeFn){
});
}
}
})
Then within my index.html:
<ul class="list-group-sm" contentEditable="true">
<li class="list-group-item" ng-repeat="child in currentBullet.children">
<bullet node="child"> </bullet>
</li>
</ul>

AngularJS: clicking on active tab removes location hash tag [duplicate]

I want to validate certain condition before the browser follow the link dynamically created by ui-router.
I was looking into $rootscope.$on('$stateChangeStart', ..) but I have no access to the controller.$scope from there. I also need to use this in several places in the application and would be cumbersome.
Keep in mind that ui-sref is linked to ui-sref-active (work together), so i can't remove ui-sref and, by say, to use $state.$go('some-state') inside a function called with ng-click.
The condition should be evaluated inside a $scope function and on on-click event (before-transition with the ability to cancel it)
I need something like this:
<li ui-sref-active="active">
<a ui-sref="somestate" ui-sref-if="model.validate()">Go Somestate</a>
</li>
I tried:
<li ui-sref-active="active">
<a ui-sref="somestate" ng-click="$event.preventDefault()">Go Somestate</a>
</li>
<li ui-sref-active="active">
<a ui-sref="somestate" ng-click="$event.stopImmediatePropagation()">Go Somestate</a>
</li>
And
<li ui-sref-active="active">
<a ui-sref="somestate">
<span ng-click="$event.stopPropagation();">Go Somestate</span>
</a>
</li>
Even
<li ui-sref-active="active">
<a ui-sref="somestate" onclick="return false;">Go Somestate</a>
</li>
But does not work.
SANDBOX
This answer inspired me to create a directive that allows me to interrupt the chain of events that end up changing state. For convenience and other uses also prevents the execution of ng-click on the same element.
javascript
module.directive('eatClickIf', ['$parse', '$rootScope',
function($parse, $rootScope) {
return {
// this ensure eatClickIf be compiled before ngClick
priority: 100,
restrict: 'A',
compile: function($element, attr) {
var fn = $parse(attr.eatClickIf);
return {
pre: function link(scope, element) {
var eventName = 'click';
element.on(eventName, function(event) {
var callback = function() {
if (fn(scope, {$event: event})) {
// prevents ng-click to be executed
event.stopImmediatePropagation();
// prevents href
event.preventDefault();
return false;
}
};
if ($rootScope.$$phase) {
scope.$evalAsync(callback);
} else {
scope.$apply(callback);
}
});
},
post: function() {}
}
}
}
}
]);
html
<li ui-sref-active="active">
<a ui-sref="somestate" eat-click-if="!model.isValid()">Go Somestate</a>
</li>
PLUNKER
You can use a scope function that will either returns :
no state
an existing state
like so :
HTML :
<li ui-sref-active="active">
<a ui-sref="{{checkCondition()}}">Go Somestate</a>
</li>
JS scope :
$scope.checkCondition = function() {
return model.validate()
? 'someState'
: '-' // hack: must return a non-empty string to prevent JS console error
}
href attribute will be created only when the function returns an existing state string.
Alternatively, you could do a (ugly) :
<li ui-sref-active="active">
<a ui-sref="somestate" ng-if="model.validate()">Go Somestate</a>
<span ng-if="!model.validate()">Go Somestate</span>
</li>
Hope this helps
The easiest workaround to conditionally achieve routing without tinkering with directives, scope etc was a workaround i found here - https://github.com/angular-ui/ui-router/issues/1489
<a ui-sref="{{condition ? '.childState' : '.'}}"> Conditional Link </a>
You can always double up on the element and show/hide conditionally
<li ui-sref-active="active">
<a ng-show="condition1" style="color: grey">Start</a>
<a ng-hide="condition1" ui-sref="start">Start</a>
</li>
http://plnkr.co/edit/ts4yGW?p=preview
No need for complicated directives or hacks. The following works fine and allows for specific handling on click of non-sref items:
<a
ng-repeat="item in items" ui-sref="{{item.sref || '-'}}"
ng-click="$ctrl.click(item, $event)"
>...</a>
And in the controller, a simple click handler for the items which don't have an item.sref:
this.click = function(item, event) {
if (!item.sref) {
event.preventDefault();
//do something else
}
};
Based on the answers to How to dynamically set the value of ui-sref you can create a function in your scope for building the URL:
$scope.buildUrl = function() {
return $state.href('somestate', {someParam: 'someValue});
};
And then conditionally append it to the link with ng-href
<a ng-href="{{ someCondition ? buildUrl() : undefined }}">Link</a>
As you can see in the demo below, ng-href does not add the href attribute if value is negative.
angular.module('app', [])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<a ng-href="{{ condition ? 'http://thecatapi.com/api/images/get?format=src&type=gif' : undefined}}">This is the link</a>
<br>
<label for="checkbox">
<input type="checkbox" id="checkbox" ng-model="condition">
Link active?
</label>
</div>
I know this is an old question, but for future reference I wanted to offer an alternative solution since I didn't see it in any of the answers so far.
Desired:
<li ui-sref-active="active">
<a ui-sref="somestate" ui-sref-if="model.validate()">Go Somestate</a>
</li>
Potential solution (template):
<li ng-class="{ active: state.current.name === 'somestate' }">
<a ng-click="navigateToState()">Go Somestate</a>
</li>
And in the controller:
$scope.state = $state;
$scope.navigateToState = navigateToState;
function navigateToState() {
if ($scope.model.valid) {
$state.go('somestate');
}
}
Possible solution for those who still need ng-click working on ui-sref component or its parents.
My solution is to use href instead of ui-sref and to modify Emanuel's directive a bit to be able to stop href and ng-click calls separately.
Planker.
Though it has a few restrictions:
will not work with ui-sref
you should have different urls for each state because of previous restriction
ui-sref-active will not work either
For the binary case (link is either enabled or disabled), it "now" (since ~2018) works like this (prevents the click and sets it to disabled):
<a ui-sref="go" ng-disabled="true">nogo</a>
and for other tags as well:
<span ui-sref="go" ng-disabled="true">nogo</span>

Angular directive two way databinding fails

I have a dropdown directive with a function which sets the active item in the dropdown:
angular.module('clientApp')
.directive('customDropDown', function ($filter, calculationsFactory) {
return {
templateUrl: 'template/custom-drop-down/custom-drop-down.html',
restrict: 'E',
scope: {
resources: '=',
activeResource: '=',
},
link: function postLink(scope) {
scope.setActiveResource = function(resource){
scope.activeResource = resource;
};
}
};
});
Markup:
<custom-drop-down resources="medias" active-resource="activeMedia"></custom-drop-down>
DirectiveTemplate:
<div class="btn-group" dropdown>
<button type="button" class="btn btn-default dropdown-toggle" dropdown-toggle style="width:100%;" ng-click="$event.stopPropagation()">
{{activeResource.title}} <span class="caret pull-right" style="margin-top:8px;"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="resource in filtredResources"
ng-click="setActiveResource(resource)"
ng-class="{active: resource === activeResource}">
<a href>{{resource.title}}</a>
</li>
</ul>
</div>
Problem is that the function don't two way bind "activeMedia" in this case. It does not get updated.
I tried to replicate the case in jsfiddle but there it works. Anything I could have missed?
Update
I have identified the issue.
My directive is placed within the accordion directive from ui-bootstrap:
<accordion>
<accordion-group heading="Resources" is-open="true">
<custom-drop-down resources="medias" active-resource="activeMedia"></custom-drop-down>
</accordion-group>
</accordion>
If I move my directive outside the accordion directive, it works. So somehow the accordion directive is interfering with my directive. Possible to solve without modifying the code for ui-bootstrap?
In your case it is a scoping issue, as discussed here.
For elements inside angular-bootstrap controls you need to use $parent. as a prefix on scope variables.
<accordion>
<accordion-group heading="Resources" is-open="true">
<custom-drop-down resources="$parent.medias" active-resource="$parent.activeMedia"></custom-drop-down>
</accordion-group>
</accordion>

Resources