AngularJS - $emit fire before $on - angularjs

I am working with AngularJS framework. I wrote a few directives with and without their own controller and they sometimes include each other.
For every course, I use my own directive to print its informations
<li ng-repeat="course in courses" on-courses-loaded="fetch_subscribed">
<course-info course="course"></course-info>
</li>
The 'on-courses-loaded' attribute is looking for the last element, I wrote it to do things when ng-repeat is over : a few data are prepared and signal is emitted for every function register as $rootScope.$on('courses_available', function() {}) (scope < actual scope)
angular.module('app').directive('onCoursesLoaded', [
'$rootScope', function ($rootScope) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true)
{
element.ready(function() {
// Prepare things, when done return promise and then ->
$rootScope.$broadcast('courses_available');
});
}
}
}
}]);
In course info directive, there is a call to another directive
<subscribe code="course.code" triggered="true"></subscribe>
With this code
<div ng-show="available">
<button type="button" class="btn btn-success" ng-hide="subscribed" ng-click="subscribe()">
<span class="glyphicon glyphicon-ok"></span> Subscribe
</button>
<button type="button" class="btn btn-danger" ng-show="subscribed" ng-click="unsubscribe()">
<span class="glyphicon glyphicon-remove"></span> Unsubscribe
</button>
</div>
And THIS link function
if (scope.triggered === true)
{
console.log('wait for emit');
$rootScope.$on('courses_available', function() {
internals.getSubscribed();
});
}
else
{
console.log('do not wait for emit');
scope.$watch('code', function(value) {
internals.getSubscribed();
});
}
The biggest issue is that signal is emitted before subscribe directive has set its $rootScope.$on('courses_available', ..)
Here you can find HTML pseudo code expanded
<li class="list-group-item" ng-repeat="course in courses | filter: search" on-courses-loaded="fetch_subscribed">
<!--<course-info course="course"></course-info> IS NEXT DIV-->
<div>
<!--<subscribe code="course.code" triggered="true"></subscribe> IS NEXT DIV-->
<div ng-show="available">
<button type="button" class="btn btn-success" ng-hide="subscribed" ng-click="subscribe()">
<span class="glyphicon glyphicon-ok"></span> Subscribe
</button>
<button type="button" class="btn btn-danger" ng-show="subscribed" ng-click="unsubscribe()">
<span class="glyphicon glyphicon-remove"></span> Unsubscribe
</button>
</div>
</div>
I need to prepare data and emit signal after ng-repeat
While 1., every content from ng-repeat must subscribe to $rootScope.$on() and before emit.
If you have any tips, thank you all.

You can detect finish event of ng-repeat as below
if (scope.$last){
window.alert("im the last!");
}
I have done something like this , so I will post my code here maybe it can help you. First I created custom directive
'use strict';
angular.module('myApp')
.directive('onFinishRender',['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit('tilesCreated');
});
}
}
}
}]);
Then used this directive with ng-repeat
<div on-finish-render="" ng-repeat="tile in tiles" ></div>
Then I listen for this event inside another directive
scope.$on('tilesCreated', function() {
// Do something here
});
P.S. Notice that in here I do all things on the same scope. But it depends on you if you want you can broadcast events on $rootScope. I just wanted to give you base idea of how to do this. I hope it will help.

Related

$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?

Angular watch directive only loads after refresh page

I am trying to make a read more directive.
Currently my directive on works once I refresh the page. I feel like I'm missing a concept here, do I need to initialize the directive into the controller? Is my watch wrong? Currently there is nothing in the controller related to this problem.
HTML:
<div data-ng-switch-when="false">
<div data-ng-bind-html="::sanitizeWidgetContent(widget.content)"
data-expand-height
data-ng-class="{true: 'widget-text-collapsed'}[elementHeight>500]"
class="widget-content">
</div>
<div class="fadeout-bottom" data-ng-show="elementHeight>500">
<button class="btn btn-default btn-fadeout" data-ng-click="elementHeight=false">Read More</button>
</div>
</div>
JS:
angular.module('myApp.directives')
.directive('expandHeight', function () {
return function (scope, el, attrs) {
scope.$watch(attrs.expandHeight, function(){
scope.elementHeight = el[0].offsetHeight;
});
}
});

uib-timepicker broken when $compile

I want to use a 'uib-timepicker' inside a popover, so as I read, I need to $compile the html to bind it to angular scope:
app.directive('bsPopover', function($compile) {
return function(scope, element, attrs) {
element.find('#formula-nro').popover({
html : true,
title : '<span>Title</span>',
content : function() {
return $compile($('#formula_form_nro').html())(scope);
// return $('#formula_form_nro').html();
}
});
};
});
If I compile the code, the component brokes, but when I don't, it's shows and behaves correctly, but it is not binded to angular scope.
This is the HTML code of the component:
<div id="formula_form_nro" class="padding-block hide">
.....
<uib-timepicker ng-model="time" minute-step="15" readonly-input="true"> </uib-timepicker>
.....
</div>
The following is the popover button:
<button id="formula-nro" type="button" class="btn btn-default" data-container="body" data-toggle="popover" data-placement="top">f(x)</button>
The button is inside a ng-repeat:
<div ng-repeat="item in items" bs-popover>
It looks like a bug, but I don't know if am I doing well. Maybe there is a workaround for this ..
thanks,

Can't create a popover with custom template

First I tried using angular-ui
<span popover-template="removePopover.html" popover-title="Remove?" class="glyphicon glyphicon-remove cursor-select"></span>
here the template is not included and no errors are provided in console. As I undestood from previous questions this capability is still in development (using v0.13.0).
Then I tried using bootstrap's popover
<span delete-popover row-index={{$index}} data-placement="left" class="glyphicon glyphicon-remove cursor-select"></span>
This is included to popover
<div id="removePopover" style="display: none">
<button id="remove" type="button" ng-click="removeElement()" class="btn btn-danger">Remove</button>
<button type="button" ng-click="cancelElement()" class="btn btn-warning">Cancel</button>
</div>
This is the managing directive
app.directive('deletePopover', function(){
return{
link: function(scope, element, attrs) {
$(element).popover({
html : true,
container : element,
content: function() {
return $('#removePopover').html();
}
});
scope.removeElement = function(){
console.log("remove"); //does not get here
}
scope.cancelElement = function(){
console.log("cancel"); //does not get here
}
}
};
});
In case of bootstrap's popover the scope is messed up. cancelElement() call does not arrive in directive neither the parent controller.
If anyone could help me get atleast on of these working it would be great.

scope.$watch ( when DOM modified by json return, how I did notice directive to add value to it.) angular

My directive :
App.directive('dropdownSelected', function() {
return {
restrict: 'E',
scope: {},
link: function(scope, element) {
scope.$watch(
function () { return element[0].childNodes.length; },
function (newValue, oldValue) {
if (newValue !== oldValue) {
alert(newValue);
}
}
)
}
}
})
My dropdown HTML file:
<div class="btn-group" ng-controller="userWorkspaces">
<button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown">
<i class="dp fa fa-gear"></i> <dropdown-Selected></dropdown-Selected><span class="caret"> </span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="workspace in workspaces">
{{ workspace.WorkspaceName }}
</li>
</ul>
</div>
Data comes from a controller in JSON format.
I fount out, directive always loaded earlier than controller, and element[0].childNodes.length and newvalue are always 0. Can anyone tell me why never call back and newValue?
As far as I understand your question, here is the solution I can think of:
1.Add following scope to your directive (here I have added dataFromController: '=') -
App.directive('dropdownSelected', function() {
return {
restrict: 'E',
scope: {
dataFromController: '='
},
link: function(scope, element) {
// set scope.dataUpdated = false
// update your element inside here using dataFromController which has value to update your directive
// once your update element here set scope.dataUpdated = true
// then add watch for 'dataUpdated' and do the stuff you wanted to do on change of that
scope.$watch(
'dataUpdated' },
function (newValue, oldValue) {
if (newValue !== oldValue) {
alert(newValue);
}
}
)
}
}
})
2.Inside HTML add attribute to your directive like this
<div class="btn-group" ng-controller="userWorkspaces">
<button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown">
<i class="dp fa fa-gear"></i> <dropdown-Selected data-from-controller="jsonObjFromController"></dropdown-Selected><span class="caret"> </span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="workspace in workspaces">
{{ workspace.WorkspaceName }}
</li>
</ul>
</div>
3.Inside your controller update $scope.jsonObjFromController with JSON data

Resources