Angular 1.6.6
There is a dashboard with widgets. I need to be able to have multiple widgets of the same type on the dashboard.
Example of widget object, type - clock:
"clock": {
"title": "World Clock",
"group": "Tools",
"name": "clock",
"description": "Display date and time",
"templateUrl": "widgets/clock/view.html",
"sizeX": 1,
"sizeY": 1,
"refresh": false,
"config": {
"title": "World Clock",
"timePattern": "HH:mm:ss",
"datePattern": "YYYY-MM-DD",
"location": "Europe/Amsterdam",
"showseconds": false
},
"api": {}
}
The HTML template is
<div gridster="gridsterOptions">
<ul>
<li gridster-item="widget" ng-repeat="widget in $ctrl.dashboard.widgets track by $index">
<p>{{$index}}</p>
<clock-widget ng-if="widget.name==='clock'" widget=widget
on-delete="$ctrl.deleteWidget(widget)" on-update="$ctrl.updateWidget(widget)">
</clock-widget>
</li> <!-- END gridster-item, widget -->
</ul>
</div> <!-- END gridsterOptions -->
Widget clock-widget
is a component.
After adding the same widget twice I see the following error in the browser console.
Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
If I don't use track by $index
I have the duplicates error:
Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys.
Also, I tried to add a unique value to each widget id
property and track by id
like this
<li gridster-item="widget" ng-repeat="widget in $ctrl.dashboard.widgets track by widget.id">
And I got the Duplicates ...
error again.
What is wrong here?
Dashboard component controller:
/*global angular*/
import { findIndex, forEach, cloneDeep } from 'lodash';
import widgets from '../data/widgets';
import addWidgetTemplate from '../dialogs/addWidget/addWidget.dialog.template.html';
import addWidgetController from '../dialogs/addWidget/addWidget.dialog.controller';
const injectParams = [
'$stateParams',
'$log',
'storeService',
'authService',
'eventbus',
'EVENTS',
'SweetAlert',
'$state',
'dialogs',
'$uibModal'
];
const MainDashboardCtrl = function($stateParams, $log, storeService, authService,
eventbus, EVENTS, SweetAlert, $state, $dialogs, $uibModal) {
const self = this;
self.$onInit = function () {
storeService.get($stateParams.boardID)
.then(function (_dashboard_) {
self.dashboard = _dashboard_;
if (!self.dashboard) {
self.dashboard = {};
}
if (!self.dashboard.widgets) {
self.dashboard.widgets = [];
}
const currentUser = authService.getCurrentLoginUser();
if (currentUser.permissions && currentUser.permissions.indexOf('admins') > -1) {
self.dashboardEditDisable = false;
}
if (status.uuid && status.uuid != currentUser.uuid) {
self.dashboardEditDisable = true;
} else {
self.dashboardEditDisable = false;
}
})
.catch(function (error) {
$log.error('[mainDashboardCtrl]', error);
});
};
self.deleteWidget = function (widget) {
const index = findIndex(self.dashboard.widgets, w => w.name === widget.name);
self.dashboard.widgets.splice(index, 1);
};
self.updateWidget = function (widget) {
const index = findIndex(self.dashboard.widgets, w => w.name === widget.name);
self.dashboard.widgets[index] = cloneDeep(widget);
};
// add widget
self.addWidget = function () {
$uibModal
.open({
controllerAs: '$ctrl',
controller: addWidgetController,
template: addWidgetTemplate,
resolve: {
widgets: function () {
return widgets;
}
}
})
.result.then(function (widget) {
self.dashboard.widgets.push(widget);
});
};
};
MainDashboardCtrl.$inject = injectParams;
export default MainDashboardCtrl;
Dashboard template:
<div class="search-results" access="users" access-permission-type="AtLeastOne">
<div class="header-action-area">
<h1 class="lath">
<small class="text-header-sip">
<i class="fa fa-h-square" aria-hidden="true" style="margin-right:5px;"></i>
{{$ctrl.dashboard.name}}
</small> <!-- END text-header-sip -->
<button ng-disabled="dashboardEditDisable" ng-click="$ctrl.addWidget()" class="btn btn-whiter btn-primary"
type="submit" ng-mouseover="hoverA=true" ng-mouseout="hoverA=false">
<i title="Add New Widget" class="glyphicon glyphicon-plus"></i>
</button>
</h1> <!-- END lath -->
</div> <!-- END header-action-area -->
<div gridster="gridsterOptions">
<ul>
<li gridster-item="widget" ng-repeat="widget in $ctrl.dashboard.widgets">
<clock-widget ng-if="widget.controllerAs=='clock'" widget=widget
on-delete="$ctrl.deleteWidget(widget)" on-update="$ctrl.updateWidget(widget)">
</clock-widget>
</li> <!-- END gridster-item, widget -->
</ul>
</div> <!-- END gridsterOptions -->
</div> <!-- search-results -->
Widget component controller:
import controller from './clock-widget.settings.controller.js';
import template from '../templates/clock-widget.settings.template.html';
import 'angular-clock/dist/angular-clock.css';
import '../style/clock-widget.css';
import timezones from '../data/timezones';
const injectParams = ['$scope', '$timeout', '$uibModal', '$log'];
const ClockWidgetCtrl = function($scope, $timeout, $uibModal, $log) {
const self = this;
const initLocation = function (widget) {
self.timezones = timezones;
self.gmtOffset = timezones[widget.config.location];
self.displayLocation = widget.config.location.split('/')[1].toUpperCase();
};
self.delete = function () {
self.onDelete({ widget: self.widget });
};
self.update = function (widget) {
initLocation(widget);
self.onUpdate({ widget });
};
self.openSettings = function () {
$uibModal
.open({
controllerAs: '$ctrl',
controller,
template,
resolve: {
widget: function () {
return self.widget;
},
timezones: function () {
return self.timezones;
}
}
})
.result.then(function (widget) {
self.update(widget);
});
};
self.$onInit = function () {
initLocation(self.widget);
};
};
ClockWidgetCtrl.$inject = injectParams;
export default ClockWidgetCtrl;