I got a directive something like below:
app.directive('testing', ['$interval', function($interval) {
return {
restrict:'E',
scope: {
data: '='
},
link: function(scope, element) {
var myInterval = $interval(function() {
scope.testData = true;
}, 1000, 0, false);
},
templateUrl: 'view/test.html'
};
}]);
In my view/test.html
<span ng-if="testData">Showing testData</span>
Initially I thought setting invokeApply to false, ng-if will not be fired.
var myInterval = $interval(function() {
scope.testData = true;
}, 1000, 0, false);
But regardless of true / false
var myInterval = $interval(function() {
scope.testData = true;
}, 1000, 0, true);
scope.testData will still be watched by ng-if.
So my question is, when I set invokeApply to false, what will be the main differences or impact to my app?
Related
Budding web developer here struggling with updating the view from my controller.
I'm using highmaps and angular to build a neat selection tool for my web app. I've got a directive nested inside the scope of a controller. I would like this directive to update a value (selectedCountry) stored in the controller. Then, I'd like the controller to display the up to date selectedCountry value on the view.
I've checked that the directive is passing the correct selectedCountry value to the parent controller. However, the controller is not updating the view to match the updated value. I would greatly appreciate if someone could take a look at this.
Demo Here: http://jsfiddle.net/frauLLmr/5/
index.html
<div ng-app="myApp">
<div ng-controller="GraphController as graphCtrl">
<div> {{graphCtrl.showSelectedCountry()}} </div>
<div> {{graphCtrl.selectedCountry}} </div>
<high-chart-directive update-selected-country='graphCtrl.updateSelectedCountry(newCountry)'></high-chart-directive>
</div>
</div>
app.js
var myApp = angular.module('myApp', []);
myApp.controller('GraphController', function() {
var self = this;
self.selectedCountry = 'unselected';
var outsideScopeTest = function() {
alert('selectedCountry (from controller scope): '
+ self.selectedCountry);
};
self.updateSelectedCountry = function(newCountry) {
self.selectedCountry = newCountry;
outsideScopeTest();
};
self.showSelectedCountry = function() {
return self.selectedCountry;
};
});
myApp.directive('highChartDirective', function () {
return {
restrict: 'E',
scope: {
updateSelectedCountry: '&'
},
link: function(scope, element) {
Highcharts.mapChart(element[0], getMapOptions(mapClick));
function mapClick(event) {
scope.updateSelectedCountry({newCountry: event.point.name});
alert('selectedCountry (from directive scope): '
+ event.point.name);
}
}
};
function getMapOptions(callback) {
return {
title: {
text: ''
},
mapNavigation: {
enabled: true,
buttonOptions: {
verticalAlign: 'bottom'
}
},
series: [{
data: getTestCountries(),
mapData: Highcharts.maps['custom/world-highres'],
// TODO-chantelle: figure out how geoJSON joinBy works
joinBy: 'hc-key',
name: 'Emission per capita',
states: {
hover: {
color: '#9370DB'
}
},
dataLabels: {
enabled: false,
format: '{point.name}'
}
}],
plotOptions: {
series: {
events: {
click: function(event) {
callback(event);
}
}
}
}
};
}
function getTestCountries() {
return [{
"hc-key": "ca",
"value": 0
}, {
"hc-key": "br",
"value": 1
}, {
"hc-key": "ru",
"value": 2
}];
}
});
the issue is that Highcharts.mapChart(element[0], getMapOptions(mapClick)); is not part of the angular scope. So any calls here will not trigger the angular app to refresh. You need to force angular to update using $scope.apply();
var outsideScopeTest = function() {
alert('selectedCountry (from controller scope): '
+ selfc.selectedCountry);
// force angular update
$scope.$apply();
};
Try this
<div ng-app="myApp">
<div ng-controller="GraphController as graphCtrl">
<div> {{graphCtrl.showSelectedCountry()}} </div>
<div> {{graphCtrl.selectedCountry}} </div>
<high-chart-directive update-selected-country='graphCtrl.updateSelectedCountry(newCountry)'></high-chart-directive>
</div>
</div>
the js
var myApp = angular.module('myApp', []);
myApp.controller('GraphController', function($scope) {
var self = this;
self.selectedCountry = 'unselected';
var outsideScopeTest = function() {
alert('selectedCountry (from controller scope): '
+ self.selectedCountry);
$scope.$apply();
};
self.updateSelectedCountry = function(newCountry) {
self.selectedCountry = newCountry;
outsideScopeTest();
};
self.showSelectedCountry = function() {
return self.selectedCountry;
};
});
myApp.directive('highChartDirective', function () {
return {
restrict: 'E',
scope: {
updateSelectedCountry: '&'
},
link: function(scope, element) {
Highcharts.mapChart(element[0], getMapOptions(mapClick));
function mapClick(event) {
scope.updateSelectedCountry({newCountry: event.point.name});
alert('selectedCountry (from directive scope): '
+ event.point.name);
}
}
};
function getMapOptions(callback) {
return {
title: {
text: ''
},
mapNavigation: {
enabled: true,
buttonOptions: {
verticalAlign: 'bottom'
}
},
series: [{
data: getTestCountries(),
mapData: Highcharts.maps['custom/world-highres'],
// TODO-chantelle: figure out how geoJSON joinBy works
joinBy: 'hc-key',
name: 'Emission per capita',
states: {
hover: {
color: '#9370DB'
}
},
dataLabels: {
enabled: false,
format: '{point.name}'
}
}],
plotOptions: {
series: {
events: {
click: function(event) {
callback(event);
}
}
}
}
};
}
function getTestCountries() {
return [{
"hc-key": "ca",
"value": 0
}, {
"hc-key": "br",
"value": 1
}, {
"hc-key": "ru",
"value": 2
}];
}
});
I am trying to write test case for my directive in anguarjs1.x
here is my directive
.directive("weeklyDirective", function($timeout) {
return {
scope: {
data: '=',
},
link: function(scope, element) {
scope.weekDays = [
{ text: 'Sun', id: 1 },
{ text: 'Mon', id: 2 },
{ text: 'Tue', id: 3 },
{ text: 'Wed', id: 4 },
{ text: 'Thu', id: 5 },
{ text: 'Fri', id: 6 },
{ text: 'Sat', id: 7 }
];
},
restrict: 'A',
templateUrl: "/flat-ui/tpls/weekly-scheduler.html",
};
})
where is my directive template
<ul style="padding: 0px;display: inline;margin: 0;list-style: none;">
<li ng-repeat="weekDay in weekDays" style="padding: 10px;display: inline;">
<input type="checkbox" value="{{weekDay.id}}" check-list="data.weeklyDetails" id="{{'chk_'+$index}}" class="ee-check"> <label class="ee-check" for="{{'chk_'+$index}}"><span></span> {{weekDay.text}}</label>
</li>
</ul>
In my weekly directive, I have used another directive which handles my checkbox list
.directive('checkList', function() {
return {
scope: {
list: '=checkList',
value: '#'
},
link: function(scope, elem, attrs) {
var handler = function(setup) {
var checked = elem.prop('checked');
var index = scope.list.indexOf(scope.value);
if (checked && index == -1) {
if (setup) elem.prop('checked', false);
else scope.list.push(scope.value);
} else if (!checked && index != -1) {
if (setup) elem.prop('checked', true);
else scope.list.splice(index, 1);
}
};
var setupHandler = handler.bind(null, true);
var changeHandler = handler.bind(null, false);
elem.bind('change', function() {
scope.$apply(changeHandler);
});
scope.$watch('list', setupHandler, true);
}
};
});
now I am trying to write test cases to test my directive
describe("weeklyDirective directive", function() {
var elm, scope, httpBackend, controller;
beforeEach(module('guideApp.directives'));
beforeEach(module('/flat-ui/tpls/weekly-scheduler.html'));
beforeEach(angular.mock.inject(function($rootScope, $compile) {
compile = $compile;
scope = $rootScope;
elm = angular.element('<div weekly-directive data="data"></div>');
compile(elm)(scope);
scope.data = {
interval: 1,
weeklyDetails: ['1'],
}
scope.$digest();
}));
it("click on check box it should get added in weeklyDetails", function() {
var e = elm.find('input[id="chk_3"]');
console.log(e);
e.trigger('click');
scope.$apply();
var isolateScope = elm.isolateScope();
expect(isolateScope.data.weeklyDetails.indexOf('4') > -1).toBeTruthy();
});
});
where I am trying to test that when user click on check box its value should get added to my array weeklyDetails which in data object (passed to the weeklydirective).
its not working as exptected for me please help me to get this working.
Thanks in Adv.
I have this directive:
import 'jquery';
import 'jquery-ui/ui/widgets/slider';
import 'jquery-ui/themes/base/core.css';
import 'jquery-ui/themes/base/slider.css';
import 'jquery-ui/themes/base/theme.css';
import './range.css';
function range() {
return {
restrict: 'E',
scope: {
min: '#',
max: '#',
startLabel: '&',
endLabel: '&',
showLabels: '#'
},
require: 'ngModel',
template: '<div><div>{{showLabels}}</div><div class="range" ng-class="{labels: showLabels}"></div></div>',
link: function($scope, $element, $attrs, ngModelController) {
var $range = $($element).find('.range');
$scope.min = $scope.min || 0;
$scope.max = $scope.max || 100;
function updateCustomProperties(range) {
$range[0].style.setProperty("--from", (range[0] / $scope.max) * 100);
$range[0].style.setProperty("--to", (range[1] / $scope.max) * 100);
}
if (!ngModelController.$modelValue) {
ngModelController.$modelValue = [$scope.min, $scope.max]
}
$scope.$watch('startLabel', function(value) {
$range.find('.ui-slider-handle').eq(0).attr('data-value', value);
});
$scope.$watch('endLabel', function(value) {
$range.find('.ui-slider-handle').eq(1).attr('data-value', value);
});
$range.slider({
min: $scope.min,
max: $scope.max,
step: 1,
values: ngModelController.$modelValue,
slide: function(event, ui) {
function updateModel() {
ngModelController.$setViewValue(ui.values);
}
if (!$scope.$$phase) {
$scope.$apply(updateModel);
} else {
updateModel();
}
updateCustomProperties(ui.values);
}
});
updateCustomProperties(ngModelController.$modelValue);
$scope.$watch('min', function(value) {
$range.slider('option', 'min', value);
});
$scope.$watch('max', function(value, oldValue) {
$range.slider('option', 'max', value);
updateCustomProperties(ngModelController.$modelValue);
});
ngModelController.$render = function() {
if (ngModelController.$modelValue) {
$range.slider('option', 'values', ngModelController.$modelValue);
updateCustomProperties(ngModelController.$modelValue);
}
};
}
};
}
//range.$inject = [];
module.exports = range;
and this usage:
<range start-label="vm.monthYear(vm.DashboardService.fromDate)"
end-label="vm.monthYear(vm.DashboardService.toDate)"
show-labels="{{vm.MonthDiff(vm.DashboardService.fromDate, vm.DashboardService.toDate) > 2}}"
max="{{vm.range.max}}"
class="slider" ng-model="vm.range.values"></range>
when the value of show-labels change to false I get the value in directive template ({{showLabels}} is false) but the class is not removed, it's always on the DOM element when I inspect it in developer tools.
Why the class is to removed?
The interpolated value is not parsed it's a string to fix it you can call JSON.parse on the interpolated value:
function range() {
return {
restrict: 'E',
scope: {
min: '#',
max: '#',
startLabel: '&',
endLabel: '&',
showLabels: '#'
},
require: 'ngModel',
template: '<div class="range" ng-class="{labels: labels}"></div>',
link: function($scope, $element, $attrs, ngModelController) {
var $range = $($element).find('.range');
$scope.min = $scope.min || 0;
$scope.max = $scope.max || 100;
$scope.$watch('showLabels', (value) => {
$scope.labels = JSON.parse(value);
});
...
}
};
}
Here in my code if I run this independently it works fine, but after integrate in my project the "onRegisterApi" method call twice. It seems some conflict with existing Ui-Grid.
Can Anyone have any idea? How to debug this and find the root cause. I already spend so much time on this
var app = angular.module('app', ['ngTouch', 'ui.grid', 'ui.grid.pagination']);
app.controller('MainCtrl', [
'$scope', '$http', 'uiGridConstants', function($scope, $http, uiGridConstants) {
var paginationOptions = {
pageNumber: 1,
pageSize: 25,
sort: null
};
$scope.gridOptions = {
paginationPageSizes: [25, 50, 75],
paginationPageSize: 25,
useExternalPagination: true,
useExternalSorting: true,
columnDefs: [
{ name: 'name' },
{ name: 'gender', enableSorting: false },
{ name: 'company', enableSorting: false }
],
onRegisterApi: function(gridApi) {
$scope.gridApi = gridApi;
$scope.gridApi.core.on.sortChanged($scope, function(grid, sortColumns) {
if (sortColumns.length == 0) {
paginationOptions.sort = null;
} else {
paginationOptions.sort = sortColumns[0].sort.direction;
}
getPage();
});
gridApi.pagination.on.paginationChanged($scope, function (newPage, pageSize) {
paginationOptions.pageNumber = newPage;
paginationOptions.pageSize = pageSize;
getPage();
});
}
};
var getPage = function() {
// server call
};
getPage();
}
]);
I have problem with bind two sliders, here it is what i have:
View:
<div ion-range-slider range-options="onHourOptions"></div>
<p>The value so far is: <span>{{hourValue | number: 2}}$/h</span></p>
<div ion-range-slider range-options="onMonthOptions"></div>
<p>The value so far is: <span>{{monthValue| number: 2}}$</span></p>
Controller:
$scope.onHourOptions = {
min: 0,
max: 90,
type: 'single',
step: 0.1,
postfix: " $/h ",
prettify: true,
hasGrid: true,
onChange: function (data) {
$scope.hourValue = data.fromNumber;
$scope.monthValue = data.fromNumber*168;
$scope.$apply();
},
};
$scope.onMonthOptions = {
min: 1600,
max: 15000,
type: 'single',
step: 10,
postfix: " $ ",
prettify: false,
hasGrid: true,
onChange: function (data) {
$scope.monthValue = data.fromNumber;
$scope.hourValue = data.fromNumber/168;
$scope.$apply();
},
};
And my directive for slider:
function ionRangeSlider() {
return {
restrict: 'A',
scope: {
rangeOptions: '='
},
link: function (scope, elem, attrs) {
elem.ionRangeSlider(scope.rangeOptions);
}
}
}
Now i would like to have something like this: When user change the value of payment for month then automatically the value of payment for hour will be updated. I have no idea how can i achieve this. I was trying to add to event something like this $scope.onHourOptions.from = data.fromNumber/168; but nothing was happens.