Ionic events are not firing inside directives - angularjs

I want to execute a function after the view gets loaded/ after entering to that view.
My directory is as follows:
app.directive('bonFormViewerFrame', function ($formStore) {
return {
restrict: 'E', //bind to element tag name
replace: true, //replace the entire markup with the template
templateUrl: 'ui/controls/bon-form-viewer-frame.html',
scope: {
form: '=' //specifies the item to be displayed.
},
controller: ['$scope', function ($scope) {
$scope.formContent = ($scope.form != null) ? $scope.form.Content : "";
$scope.$on("$ionicView.beforeEnter", function (event, data) {
// handle event
console.log("State Params: ", data.stateParams);
});
$scope.$on("$ionicView.enter", function (event, data) {
// handle event
console.log("State Params: ", data.stateParams);
});
$scope.$on("$ionicView.afterEnter", function (event, data) {
// handle event
console.log("State Params: ", data.stateParams);
});
}]
};
});
None of the above $ionicView events are firing. Can anyone help me out? Or what i'm doing wrong here?

None of the above $ionicView events are firing.
Well, if you will check ionic.bundle.js you can find following rows:
enteringScope.$emit('$ionicView.enter', enteringData);
enteringScope.$broadcast('$ionicParentView.enter', enteringData);
For ref:
$broadcast -- dispatches the event downwards to all child scopes,
$emit -- dispatches the event upwards through the scope hierarchy.
$ionicView uses $emit
The problem is, your directive has different scope - the child of your main controller
So you need $ionicParentView a.e.:
$scope.$on("$ionicParentView.beforeEnter", function (event, data) {
// ...
});
Demo Plunker
Results:
~+~+~ CTRL~+~+~
CTRL: on beforeEnter
~+~+~ DIR ~+~+~
CTRL: on enter
DIR: on enter
CTRL: on afterEnter
DIR: on afterEnte
However if your directive load late and ionic broadcasts these events before your directive controller regestered to events - you have nothing to do with it

Related

angular 1.5 component binding not updated from certain events

I have an angular 1.5 component (i.e. myCarousel: it wraps a bootstrap carousel) that triggers an event and exposes it with a function binding.
A parent container (i.e. myContainer) component binds a function to that event and modifies some inner state when triggered.
Another child-component of the parent container is bound to the container's inner state.
The problem:
When I trigger the event on myCarousel from a button click event, everything works fine and I can see the child-component's binding being updated.
However, when I trigger the event from a carousel slide event (slide.bs.carousel), the binding does not get updated.
Here's a plunker to demonstrate:
https://plnkr.co/edit/AHxaX8o0sE94Nfir7DVB
Can anyone explain why this is happening and how to solve this?
Some relevant code:
mainApp.component("myCarousel", {
templateUrl: "myCarousel.html",
bindings: {
onEventTriggered: "&"
},
controllerAs: "vm",
controller: function() {
let vm = this;
vm.$onInit = function() {
console.log("init!");
$("#theCarousel").carousel();
$("#theCarousel").on("slide.bs.carousel", (event) => {
console.log("sliding " + event.direction);
vm.onEventTriggered();
});
};
}
});
mainApp.component("myContainer", {
templateUrl: "myContainer.html",
controllerAs: "vm",
controller: function() {
let vm = this;
vm.counter = 0;
vm.triggerEvent = function() {
console.log("event!");
vm.counter++;
}
}
});
mainApp.component("childComponent", {
template: "<div>Event {{vm.attribute}}</div>",
controllerAs: "vm",
bindings: {
attribute: "<"
}
});
One way to do this is :
controller: function($scope) {
let vm = this;
vm.$onInit = function() {
console.log("init!");
$("#theCarousel").carousel();
$("#theCarousel").on("slide.bs.carousel", (event) => {
vm.onEventTriggered();
console.log("sliding " + event.direction);
$scope.$apply();
});
};
}
using $scope.$apply()
updated plunkr : https://plnkr.co/edit/8yZOyuQfofdoYz00tFBk?p=preview
You need to use $scope.$apply(), because $("#theCarousel").on("slide.bs.carousel", ... is jquery code, and you need to notify angular about it using $scope.$apply()

Angularjs send data from one directive to other via controller

Currently, I have two directives and parent controller, all on some form.
I would like to implement "related field"-like functionality. That is, data from one directive should be passed to other preferably through controller so that I have full flexibility in "routing" data between fields.
So I have this set-up:
Controller:
$scope.$watch('form.model.object', function (newValue, oldValue) {
$scope.$broadcast("cmsRelatedChanged", {key: 'subcarriers', value: newValue['#identity']});
};
Second directive:
$scope.$on('cmsRelatedChanged', function(event, aditionalData) {
$scope.related[aditionalData.key] = aditionalData.value;
console.log("[CMSCollectionDirective] updated with: ", aditionalData, " event: ", event);
});
It do not work first time, when form is pre-populated with existing object But following changeds made in browser work.
As if second directive $on registered that listener after first $broadcast was made.
Additional info:
Everything in second controller is done in link:, and second directive is also second in DOM.
Questions:
How can I delay that first broadcast enough for $on to register listener?
EDIT:
Added console.log("Adding listener") before $scope.$on, and it in fast is executed after first $broadcast, and that's why its not cough.
Maybe try to not use $broadcast and $on. For me this solution works to share data between directives:
JS:
app
.controller('MainCtrl', function () {
$scope.sharedData = {
data: 1
};
$scope.testAction = function () {
vm.sharedData.data++;
}
})
.directive("dirFirst", function () {
var directive = {
link: link,
restrict: 'AE',
scope: {
sharedData: '='
}
};
return directive;
function link(scope, element) {
scope.$watch(function () {
return scope.sharedData.data;
}, function () {
console.log("dirFirst: " + scope.sharedData.data)
});
element.on('click', function () {
scope.sharedData.data++;
console.log(scope.sharedData.data);
});
}
})
.directive("dirSecond", function () {
var directive = {
link: link,
restrict: 'AE',
scope: {
sharedData: '='
}
};
return directive;
function link(scope, element) {
scope.$watch(function () {
return scope.sharedData.data;
}, function () {
console.log("dirSecond: " + scope.sharedData.data)
});
element.on('click', function () {
scope.sharedData.data++;
console.log(scope.sharedData.data);
});
}
});
HTML usage:
<button dir-first shared-data="sharedData">
Directive1
</button>
<button dir-second shared-data="sharedData">
Directive2
</button>
<button ng-click="testAction()">ControllerAction</button>
Biggest issue in my question ended up being order of initialization of directives and inability to use $scope as transfer medium between controller and 2nd directive.
Solution would be to use service as such medium.
Controller still register watch on 1st directive, and as soon as it gets corresponding event put received data in service.
Data availability is signalled to 2nd directive by emmiting event. However 2nd directive check availability of data in service on its own on start up.
This way 2nd directive can be initialized long after some data was sent, and still act upon it.

Two way data binding with directive doesn't work

I have a controller used to add tasks. On that page a user needs to select a group to act upon. I have written a directive that is used to allow a user to pick groups (folders)
My page controller
function AddTaskController($scope) {
var vm = this;
vm.group = { whatsit: true };
$scope.$watch("vm.group", function () {
console.log("controller watch", vm.group);
},true);
}
The page html where the directive is used
<em-group-selection group="vm.group"></em-group-selection>
The directive configuration
function GroupSelectionDirective() {
return {
scope: {
group: '='
},
controller: GroupSelectionDirectiveController,
controllerAs: 'vm',
templateUrl: '/views/templates/common/folderselection.html'
};
}
The directive controller:
function GroupSelectionDirectiveController($scope) {
var vm = this;
$scope.$watch("group", function () { console.log("yo1", vm.group); }, true)
$scope.$watch("vm.group", function () { console.log("yo2", vm.group); }, true)
}
Now when this fires, both console.log() calls in the directive fire once, with undefined. They never fire again. If in the controller I set vm.group to something else the $watch in the AddTaskController never gets fired.
Why isnt the data binding working?
Update:
I notice that if, in the directive, I change the init() function in my directive to use $scope it works! Can I not, as Fedaykin suggests, use controllerAs with two way data binding?
function init() {
$timeout(function () {
$scope.group.shizzy = 'timeout hit';
}, 200);
}
Turns out that if you use isolate scopes and controlelrAs syntax you need to also use bindToController : true. Without this you will not be able to only use vm and will have to use $scope for the isolate scope variables
More information can be found in the John Papa style guide and this SO answer
The final directive setup is as so:
function GroupSelectionDirective() {
return {
scope: {
group: '='
},
controller: GroupSelectionDirectiveController,
controllerAs: 'vm',
bindToController: true,
templateUrl: '/views/templates/common/folderselection.html'
};
}

$scope.$emit/$broadcast not working in Custom Directive Communication in angular JS

I am using HighCharts for representing Stacked Bar Charts as well as Line Charts.My Requirement is onClick of Stacked Bar Chart the Line Chart should be displayed corresponding to the portion clicked.I acheived it using $broadcast and $on. When i use $emit in highchart.js it is not working, but when i use $broadcast or $scope.$parent.$broadcast it works. Can someone please let me know why the upward flow of event is working and downward its not working. My both directive are reffering to same Controller.
In my case why $scope.$emit or $scope.$broadcast alone does'nt work?
index.html
<div ng-controller='MainCtrl'>
<chart values='basicAreaChart' id="container" technology="3G" url="../json/HighCharts.json"></chart>
<linechart values='basicLineChart' id="container2" technology="LTE" click='clicked'></linechart>
</div>
controller.js
angular.module('chartsExample', ['lineChart','highchart']).controller('MainCtrl',
[ '$scope', '$http', function($scope, $http) {
} ]);
highchart.js
'use strict';
angular.module('highchart', []).directive('chart', function() {
return {
restrict: 'E',
scope: {
chartData: "=values"
},
transclude: true,
replace: true,
controller:function($scope,$element,$attrs,$http){
$http.get($attrs.url).success(function(data) {
$scope.chartData = data;
console.log('technology='+$attrs.technology+' '+$scope.a);
});
},
link: function($scope, $element, $attrs,$rootScope) {
'+$attrs.type);
$scope.$watch('chartData', function(value) {
if (!value){
//console.log('In return');
return;
}
// Initiate the chartData.chart if it doesn't exist yet
$scope.chartData.chart = $scope.chartData.chart ;
$scope.chartData.chart.renderTo =$attrs.id
$scope.chartData.chart.events = {
click : function() {
console.log('Chart clicked '+$scope.isClicked+' '+$scope.a);
$scope.click="false";
$scope.$parent.$broadcast('HIGH_CHART_CLICKED', 'clicked');
//This works but $scope.$emit or $scope.$broadcast does'nt work???
}
};
$scope.chartObj = new Highcharts.Chart($scope.chartData);
},true);
}
};
});
linechart.js
'use strict';
angular.module('highchart', []).directive('chart', function() {
return {
restrict: 'E',
scope: {
linechartData : "=values"
},
transclude: true,
replace: true,
controller:function($scope,$element,$attrs,$http){
$scope.$on('HIGH_CHART_CLICKED',function(e,data){
console.log('Line chart Listening event');
$http.get("../json/NewLineChart.json").success(function(data) {
$scope.linechartData = data;
console.log('this is from line chart');
});
});
},
link: function($scope, $element, $attrs,$rootScope) {
'+$attrs.type);
$scope.$watch('linechartData', function(value) {
if (!value){
//console.log('In return');
return;
}
// Initiate the chartData.chart if it doesn't exist yet
$scope.chartData.chart = $scope.chartData.chart ;
$scope.chartData.chart.renderTo =$attrs.id
}
};
$scope.chartObj = new Highcharts.Chart($scope.chartData);
},true);
}
};
});
Ok. You have two directives with the same name (chart). This answer assumes the second is actually supposed to be linechart
chart and linechart are siblings. So when you $broadcast an event from chart, it has no children, and no one hears it.
Likewise, if you $emit and event from chart, then it will get emitted to the parent scope (MainCtrl) and then on up the hierarchy to $rootScope. It will never get to linechart
When you use $scope.$parent.$broadcast, then you are telling MainCtrl to broadcast the event to it's children, which happen to be chart and linechart so it works.
Long story short - angular is working as expected here.
Unlike browser events, angular events don't bubble down and then back up. They are one way.
You could have MainCtrl listen for the event and then re-broadcast it:
In MainCtrl:
$scope.$on('HIGH_CHART....', function() {
//rebroadcast to children
var args = Array.prototype.shift.apply(arguments);
$scope.$broadcast('HIGH_CHART.....', args )
};
(or something similar. You have to remove the first element from arguments and then call broadcast with the remaining arguments - outside the scope of the question).

angular - event of a directive handled by parent controler

everyone ! I've this on my directive template...
<div class="priceSlider" ui-slider="slider.options" min="{{min}}" max="{{max}}"
step="0.01" use-decimals ng-model="sliderVal"><div>{{currency}} {{sliderVal[0]}} -
{{currency}} {{sliderVal[1]}}</div>
..and I've this on my JS for the directive
angular
.module('app.directives.categoryHead', [])
.directive('categoryHead', function() {
return {
restrict : 'E',
controller: function($scope){
$scope.sliderVal = [$scope.min , $scope.max];
$scope.slider = {
'options': {
range: true,
start: function (event, ui) { console.log('Event: Slider start - set with slider options', event); },
stop: function (event, ui) { console.log('Event: Slider stop - set with slider options', event); }
}
}
},
scope: {
language : "=",
currency : "=",
breadcrumb : "=",
min : "=",
max : "="
},
templateUrl: "templates/directives/categoryHead.html"
}
});
...and on my route template, I've this...
<category-head breadcrumb="breadCrumb" min="categoryList.minPrice"
max="categoryList.maxPrice" language="webshop.language"
currency="webshop.culture.currency"></category-head>
So, basically I've a slider...that's fires the events starts and stop - And this works really fine.
But I'd like to handle the event not inside the directive, but on the controller of the route template.
How can I "transfer" the event from the directive to the template controller ? I just need to fire a "something changed" and send a notification of the new values.
ty !
There are probably lots of different ways to do this. One option is to make your directive take event handlers that you have defined in your controller, like this:
<your-directive on-start="startHandler()" on-stop="stopHandler()></your-directive>
Then, in your directive config:
scope: {
onStart: '&',
onStop: '&'
}
Then, add something like this to your start/stop methods within the directive.
$scope.slider = {
'options': {
range: true,
start: function (event, ui) { if (onStart) onStart(event); /* other stuff */ },
stop: function (event, ui) { if (onStop) onStop(event); /* other stuff */ }
}
}

Resources