I am new to Angular 1.6 and UI-grid, so I am struggling with something simple. I want to have one UI-grid defined in my main controller that gets redefined and re-used with data from different Angular controllers. More specifically, I would like to have multiple .CSHTML grid templates using a single grid definition in the main Angular controller. I would like to fill the grid data array from different Angular controllers, with different data and different column definitions. There will only ever be one active grid at a time.
Using a separate grid definition for each controller works, but I may eventually have 20 grids; so I don't want that much repeatable bloat in my main Angular controller. The only parts of the grid definition that change are the column definitions, and the data. A simplified project layout is below to help the discussion.
In my main Angular controller, I have a UI-grid definition that I want to re-use from multiple Angular controllers in different files. The grid definition is below:
//MainController in app.js
(function () {
'use strict';
angular.module('CRNApp', ['ui.bootstrap', 'ui.grid', 'ui.grid.autoResize', 'ui.grid.emptyBaseLayer', 'ui.grid.selection']);
MainController.$inject = ['$scope', '$http', '$uibModal', '$log','uiGridConstants'];
angular.module('CRNApp')
.controller('MainController', MainController);
$scope.policyList = { //grid options and data for policy data
rowHeight: 40,
enableFiltering: true,
enableRowSelection: true,
enableRowHeaderSelection: false,
data: 'policyData',
columnDefs: [ //the column defs will be different for each different controller
{ name: 'PolicyId'},
{ name: 'PolicyState'},
{ name: 'AgentName' },
{ name: 'InforcePremium' },
{ name: 'LOB'}
]
};
$scope.policyData = []; //stores policy array returned from MVC controller to UI
$scope.policySelected = 'false'; //tracks if a row is selected. Allows row-depenendent nav buttons to be enabled on bav bar
$scope.mySelectedPolicy = {};
//grid options for processor grid
$scope.policyList.multiSelect = false;
$scope.policyList.modifierKeysToMultiSelect = false;
$scope.policyList.noUnselect = false;
$scope.policyList.onRegisterApi = function (policyApi) {
$scope.policyApi = policyApi;
$scope.policyApi.selection.on.rowSelectionChanged($scope, function (row) {
if (row.isSelected) {
$scope.policySelected = 'true';
$scope.mySelectedPolicy = row.entity; //copy selected row so that we can access it from processor controller
}
else
$scope.policySelected = 'false';
});
};
//this function recalculates the height of the visible grid after the row count changes
$scope.getPolicyTableHeight = function () {
var rowHeight = 30; // your row height
var headerHeight = 30; // your header height
return {
height: ($scope.policyList.data.length * rowHeight + headerHeight) + "px"
};
};
}());
In the main _Layout.cshtml, there is a button on a navigation bar that is bound to a function in a separate Angular controller called "PolicyController". The name of the function is called "loadPoliciesGrid()". This function fills the policyData array in the policyList grid definition with data (see main controller above). I will have multiple buttons on the page that call a similar function in different controllers. For example, I will have a Processor button that calls a "LoadProcessors()" function in a ProcessorController Angular file. The "LoadProcessors()" function would re-use the same grid definition in the main controller, but the column defs and grid array would be different.
<input style="margin-top: 8px !important; margin-bottom: 8px !important; margin-left: 8px !important;" type="submit" value="Get Records" ng-controller="PolicyController" ng-click="loadPoliciesGrid()" class="btn btn-default btn-sm">
The function in the "PolicyController" to load the grid data is below. There will be other Angular controllers that call their own data and the grid definition in the main Controller should somehow have the appropriate column defs:
//PolicyController in policy.js
(function () {
angular.module('CRNApp');
PolicyController.$inject = ['$scope', '$http', '$uibModal', '$log'];
angular.module('CRNApp')
.controller('PolicyController', PolicyController);
function PolicyController($scope, $http, $uibModal, $log) {
$scope.loadPoliciesGrid = function () {
$scope.$parent.loading = true;
$http.get("/api/Policy")
.then(function (responsePol) {
$scope.$parent.policyData = responsePol.data;
$scope.$parent.showGrid = 'policies';
$scope.$parent.loading = false;
})
.catch(function (error) {
console.log(error);
});
};
};
}());
The grid template is bound to the policy controller in the Index.cshtml page, which gets loaded as a child page in the main _Layout.cshtml page. Each one of these templates should bind their data to the same UI-grid.:
@*Index.cshtml. Each template is bound to a different Angular controller*@
@{ViewBag.Title = "ICS Portal - Home Page";}
<div ng-include="'templates/policiesGrid.cshtml'" ng-controller="PolicyController"></div>
<div ng-include="'templates/processorGrid.cshtml'" ng-controller="ProcessorController"></div>
<div ng-include="'templates/usersGrid.cshtml'" ng-controller="UserController"></div>
The grid markup container is defined in a separate template file, called policiesgrid.cshtml. Each grid markup template is in a separate file but shoudl reference the same UI-grid definition:
<div style="margin-left: auto; margin-right: auto;">
<div ui-if="policyList.data.length>0" ng-style="getPolicyTableHeight()"
ui-grid-auto-resize ui-grid-empty-base-layer ui-grid-selection id="policyGrid"
ui-grid="policyList" class="processorGrid" ng-show="showGrid == 'policies'"
style="margin-left: auto; margin-right: auto;">
</div>
</div>
You should use services to getting your data, but anyway, there are quite a few ways to achieve what you want (as everything in AngularJS). If you don't want to refactor your code, and if I understood correctly you want basic communication between controllers.
Easiest for you would be to set up watcher on your controller with grid.
So on your controller where grid is set up do something like:
$scope.$on('dataGridChangedMessage', (event, argumentsPassedToGrid) => {
$scope.policyList.columnDefs = argumentsPassedToGrid.columnDefs;
$scope.policyList.data = argumentsPassedToGrid.data;
// you might neeed here to refresh the grid using API, something like: policyApi.grid.refresh();
// or try to trigger digest cycle manually if it doesn't work with: $timeout($scope.$apply());
});
And on your other controllers you will need to inject $rootScope and broadcast your data/columnDefs - when your $http promise is resolved:
$rootScope.$broadcast('dataGridChangedMessage', {
columnDefs: [], // this is your new column defs
data: [] // this is your new data
};
so you would have ui grid controller displaying data and controllers sending data something like:
controller1 => controller UI GRID <= controller2
Even though above will work, better way for this would be:
create a brand new service,
inject service in controller UI GRID,
remove those controllers (controller1 and controller2 in my example)
return $http promise from service - or even better set up $resource and use it for different endpoints.
get different data from service (in UI GRID controller) and once promises are resolved you would just refresh grid data (similar to above example)
so use only one controller and one service - there's no need to do $broadcasts
If you are worried about repetition you can set up generic component that will display your grid data, set $resource on service and generic function - that will take two or whatever number of parameters which you could set up in template. Like this if you are using components (which you should) / directives:
<my-ui-grid-generic-component url-key="some-name-1" url-params="{something: whatever}"></my-ui-grid-generic-component>
and in service you would have defined object with keys - endpoints like this:
let uigridDataAPIs = {
'some-name-1': 'http://google.com',
'some-name-2': 'http://google.com',
};
once you inject that service in your main controller you only need a function on generic component/directive controller that will call service $resource - something like:
MyInjectedService.getResource($scope.urlKey, $scope.urlParams).then(resolve => {
// logic to update grid - similar to first example
});
You will need to set up either generic $http function or $resource that returns promise on that service. Don't forget to set up urlKey and urlParams bindings on your component/directive.
Also if you want to learn even more advanced way of doing things check this:
I highly recommend this one: https://github.com/toddmotto/angularjs-styleguide
https://github.com/angular-redux/ng-redux
https://rxjs-dev.firebaseapp.com/
Keep in mind all above is just to point you in right direction for the future, I haven't tested anything I wrote - most of it is hypothetical and way I had it set up on project with ui-grid's, so you will need to fill in gaps, once you get to next step and get stuck - post a new question and someone will help.