AngualrJs: ag-grid in a ui-router view (templateUrl) - angularjs

I had a working app, including a few ag-grids. I decided to add ui-router, with the grids in the templateUrl of a state.
The code is mcuh to large to post, but I have two problems:
document.addEventListener("DOMContentLoaded" is not called when I put it in the controller of the templateUrl
I guess that I can get round that by moving the enclosed logic into $transitions.onSuccess({ entering: 'search_result' }, function (transition), BUT, when I
const currentCandidatesGridDiv = document.querySelector('#currentCandidatesGrid');
new agGrid.Grid(currentCandidatesGridDiv, Self.currentCandidatesGrid);
I find that currentCandidatesGridDiv is null, despite having
<div ag-grid="SearchResultController.currentCandidatesGrid"></div>
in the HTML of the templateUrl.
Again, not much help to you without full code, which is very, very large.
I guess that what I am looking for is a working code sample, Plunk, etc to show how to put a simple ag-grid into a ui-router state's templateUrl.

It looks like your actual problem is that you are using a querySelector on the id
#currentCandidatesGrid
# is a selector for an element id
This would only match your element if it had that specified id, which in your example does not exist.
<div ag-grid="SearchResultController.currentCandidatesGrid"></div>
Would need to be
<div id="currentCandidatesGrid" ag-grid="SearchResultController.currentCandidatesGrid"></div>
if you want to get that element via
document.querySelector('#currentCandidatesGrid');

This answer has three parts:
Why DOMContentLoaded event listeners fail in controllers
Use custom directives to inject code that manipulates DOM
DEMO of ag-Grid with AngularJS
Why DOMContentLoaded event listeners fail in controllers
JavaScript libraries that manipulate the DOM need to coordinate with DOM manipulations done by the AngularJS framework.
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc.
document.addEventListener("DOMContentLoaded" is not called when I put it in the controller of the templateUrl
The AngularJS framework initializes itself after the DOMContentLoaded event. So naturally any DOMContentLoaded event listener added afterwards by a controller will miss that event.
One should use caution when mixing AngularJS with third-party libraries that manipulate the DOM.
Use custom directives to inject code that manipulates DOM
When one sees code such as document.querySelector("#myid'), replace that with a custom directive:
app.directive("myDirective", function() {
return {
link: postLink
};
function postLink(scope, elem, attrs) {
//DOM initialization here
//e.g. initialize(elem);
scope.$on('$destroy', function() {
//DOM teardown code here
});
}
})
Usage:
<div id="myid" my-directive></div>
AngularJS directives are markers on a DOM element that tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element.
When the AngularJS framework adds templates to the DOM, it parses the markup and injects code for the AngularJS directives. When it destroys DOM, it broadcasts a $destroy event on the scope associated with the element.
For more information, see
AngularJS Developer Guide - Creating Custom Directives
DEMO of ag-Grid with AngularJS
When the ag-Grid script loads, it does not register with AngularJS 1.x. This is because AngularJS 1.x is an optional part of ag-Grid and you need to tell ag-Grid you want to use it:
agGrid.initialiseAgGridWithAngular1(angular);
angular.module("example", ["agGrid"])
For more information, see
Ag-Grid Documentation - Basic AngularJS 1.x Example
The DEMO
agGrid.initialiseAgGridWithAngular1(angular);
angular.module("example", ["agGrid"])
.controller("exampleCtrl", function($scope) {
var columnDefs = [
{headerName: "Make", field: "make"},
{headerName: "Model", field: "model"},
{headerName: "Price", field: "price"}
];
var rowData = [
{make: "Toyota", model: "Celica", price: 35000},
{make: "Ford", model: "Mondeo", price: 32000},
{make: "Porsche", model: "Boxter", price: 72000}
];
$scope.gridOptions = {
columnDefs: columnDefs,
rowData: rowData
};
})
html, body {
height: 100%;
width: 100%;
margin: 0;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
html {
position: absolute;
top: 0;
left: 0;
padding: 0;
overflow: auto;
}
body {
padding: 1rem;
overflow: auto;
}
<script src="//unpkg.com/angular/angular.js"></script>
<script src='//unpkg.com/#ag-grid-community/all-modules/dist/ag-grid-community.min.js'>
</script>
<body ng-app="example" ng-controller="exampleCtrl" style="height: 100%">
<div ag-grid="gridOptions" class="ag-theme-balham" style="height: 100%;">
</div>
</body>

Related

AngularJs - ag-grid rowData using "controller as" syntax

[Update] This Plunker does what I want, but :
it uses ui-grid, rather than ag-grid.
it injects the grid into the module, rather than just the single controller which uses it.
I presume that these changes would be straightforward & will try to work them into my code when I get home in about 14 hours time.
If anyone wants to fork that Plunk and make those changes, I will award a bounty, as this is a good basic start point demo for others wanting to do the same, so that a Plunker would be of general help.
I am tantalizingly close, but
Cannot read property 'setRowData' of undefined (caused by "<ui-view class="ng-scope ng-binding">")"TypeError: Cannot read property 'setRowData' of undefined
I am using "controller as" syntax, hence the Self; (Self = this;). That is working fine, my problem is when I try to set the rowData for an ag-grid in the templateURL of a ui-router state.
It's much to big to post, but here's the relevant stuff:
<div id="currentCandidatesGridDiv"
ag-grid="Search_result_controller.currentCandidatesGrid"
class="ag-theme-balham red_border"
style="height: 30%; width:90%">
</div>
// lookup the container we want the Grid to use
const currentCandidatesGridDiv = document.querySelector('#currentCandidatesGridDiv');
// create the grid passing in the div to use together with the columns & data we want to use
new agGrid.Grid(currentCandidatesGridDiv, Self.currentCandidatesGrid);
Self.currentCandidatesGrid =
{
columnDefs: [
{ headerName: "Candidate", field: "candidate_name", sortable: true },
{ headerName: "Skills", field: "skills", sortable: true },
{ headerName: "Start date", field: "start_date", sortable: true }
],
rowData: [],
pagination: true,
paginationAutoPageSize: true,
};
Was I correct to rowData: [], or ought I to have rowData: <someVariable>?
Then I calculate the row data into an array, Self.currentCandidatesGridRowData.
When I try to Self.currentCandidatesGrid.api.setRowData(Self.currentCandidatesGridRowData); I get error showing above.
I searched, but cannot find a working Plunker using the controller as syntax.
[Dupers] 1) the "dupe" question does not have an answer, so is of no use to me
2) my question is specifically about using Self.xxxGrid.api.setRowData(Self.xxxGridRowData); with the `controller as syntax. Pleas ere-open. Thnaks
DEMO of ag-Grid with AngularJS using "controller As" syntax
When the ag-Grid script loads, it does not register with AngularJS 1.x. This is because AngularJS 1.x is an optional part of ag-Grid and you need to tell ag-Grid you want to use it:
agGrid.initialiseAgGridWithAngular1(angular);
angular.module("example", ["agGrid"])
For more information, see
Ag-Grid Documentation - Basic AngularJS 1.x Example
The DEMO on PLNKR
agGrid.initialiseAgGridWithAngular1(angular);
angular.module("example", ["agGrid"])
.controller("exampleCtrl", function() {
var columnDefs = [
{headerName: "Make", field: "make"},
{headerName: "Model", field: "model"},
{headerName: "Price", field: "price"}
];
var rowData = [
{make: "Toyota", model: "Celica", price: 35000},
{make: "Ford", model: "Mondeo", price: 32000},
{make: "Porsche", model: "Boxter", price: 72000}
];
this.gridOptions = {
columnDefs: columnDefs,
rowData: rowData
};
})
html, body {
height: 100%;
width: 100%;
margin: 0;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
html {
position: absolute;
top: 0;
left: 0;
padding: 0;
overflow: auto;
}
body {
padding: 1rem;
overflow: auto;
}
<script src="//unpkg.com/angular/angular.js"></script>
<script src='//unpkg.com/#ag-grid-community/all-modules/dist/ag-grid-community.min.js'>
</script>
<body ng-app="example" ng-controller="exampleCtrl as $ctrl"
style="height: 100%">
<div ag-grid="$ctrl.gridOptions" class="ag-theme-balham"
style="height: 100%;">
</div>
</body>
Was I correct to rowData: [], or ought I to have rowData: <someVariable>?
You can even ignore this property in gridOptions, but the difference is that:
if you will define rowData as an empty array - grid will render handle it as 'No rows to show', but if you will not define it, grid will show 'Loading...' - which is more correct, with a delayed request case.
Besides, you can handle overlay logic by yourself for more details check here.
Now, as I said in comments
Cannot read property 'setRowData' of undefined
this issue could be caused by using the wrong reference.
First, take a look and check this answer with ag-grid developer comment also.
Secondly, about an issue itself:
.controller('yourController', [function() {
var Self = this; // here's your controller reference
// then you will have columns and rowData (probably)
// but the most important part is here
Self.currentCandidatesGrid = {
... // anything that you need
onGridReady: gridReady // major point for future api reference
}
function gridReady(params){
Self.gridApi = params.api;
Self.columnsApi = params.columnsApi;
}
}]);
And after gridReady you would be able to use grid API methods via Self.gridApi
Demo

Unable to render the calendar properly for FullCalendar AngularJS Directive

I am new to AngularJS and have to incorporate an Admin LTE full calendar feature in my webapp. I found an Angular directive for the Arshaw FullCalendar JQuery plugin. The link to the directive is here: http://angular-ui.github.io/ui-calendar/
Although I am able to display the calendar, I am not able to get the previous, today and next feature working for the calendar. This help to toggle between the months of the calendar. I know this feature is inside the uiConfig but I am not able to understand why this feature doesn't get rendered.
Below is my code. I have included all the files necessary for the plugin but did not show in index.html.
index.html
<div class="col-xs-12" resize>
<h1>Calendar Page</h1>
<div class="calendar" ng-model="testevents" config="uiConfig.calendar" ui-calendar="{{uiConfig.calendar}}"></div>
calendarController.js
app.controller("calendarController", ["$scope", "displayCalendar", "dialogs", "$filter", function ($scope, displayCalendar, dialogs, $filter) {
$scope.$parent.pageTitle = "Displays Reporting Period Start and End Dates";
/* config object */
$scope.uiConfig = {
calendar: {
height: 450,
editable: true,
header: {
left: 'title',
center: '',
right: 'today prev,next'
},
eventClick: $scope.alertOnEventClick,
eventDrop: $scope.alertOnDrop,
eventResize: $scope.alertOnResize,
eventRender: $scope.eventRender
}
};
$scope.form = {};
$scope.testevents = {
events: [
{
title: 'event1',
start: '2015-12-12'
},
{
title: 'event2',
start: '2015-12-11'
}
// etc...
],
color: 'yellow', // an option!
textcolor: 'black' // an option!
};
}]);
UPDATE:
looking at the demo code on their github page
<div class="calendar" ng-model="eventSources" calendar="myCalendar1" ui-calendar="uiConfig.calendar"></div>
I think you would need to change yours to:
<div class="calendar" ng-model="testevents" calendar="myCalendar1" ui-calendar="uiConfig.calendar"></div>
here is the .html page for their demo
https://github.com/angular-ui/ui-calendar/blob/master/demo/index.html
Have you also added the ui-calendar component to the app initialisation?
'var myAppModule = angular.module('myApp', []);'
inside the [] normally you have to inject a third party component so it can be seen by the rest of your controllers.
Ok.. So after hours and hours of debugging, I figured out what the issue was.
I included fullcalendar.print.css also which was throwing off the styling for fullcalendar.css.
So an advice peeps, if the buttons are not getting rendered properly u might need to consider checking the CSS and remove fullcalendar.print.css.

Using KendoUI methods in AngularJS

How can I use the methods provided by KendoUI widgets in an AngularJS application?
For the configuration of the widgets the directive attributes are used within the markup via k- prefixes, I know.
But in the documentation, there are a lot of methods assigned to the widgets.
For example the kendo-mobile-tab-strip widget has a switchTo method.
The example shows to use it this way (without angular)
var app = new kendo.mobile.Application();
function onClick() {
var tabstrip = app.view().footer.find(".km-tabstrip").data("kendoMobileTabStrip");
tabstrip.switchTo("#bar"); //activate "bar" tab
}
How can I access the app variable in an Angular controller, when I just use a <kendo-mobile-application> directive to initialize the app?
Is there any other (proper) way of using the widget methods?
I'm curious of the best practice here, because this all feels not like Angular...
When you supply a value to the widget's attribute (or to a k-scope-field attribute), that becomes the name of the scope property which you can use to get a reference to the widget.
HTML
<div ng-controller="MainCtrl">
<div kendo-grid="myGrid"></div>
<!-- or -->
<div kendo-grid k-scope-field="myGrid"></div>
</div>
Controller
angular.controller("MainCtrl", function($scope) {
// the widget is accessible from the scope
$scope.myGrid.doSomething();
});
You're right, calling methods on widget isn't like Angular. So in most cases you want to be putting this in your own directives, wrapping kendo's widgets. This lets you keep DOM specific code out of your application controllers and contained in directives, where they belong.
HTML
<div ng-controller="MainCtrl">
<my-grid-directive></my-grid-directive>
</div>
Directive
angular.controller("myGridDirective", function() {
return {
template: "<div kendo-grid='myGrid'></div>",
link: function (scope, element, attrs) {
scope.myGrid.doSomething();
}
};
});
I never using kendo mobile application so it might be different but in general to access the kendo object in the $scope object by adding identifier in kendo attribute per example below.
<div ng-app="app" ng-controller="MyCtrl">
<input kendo-datepicker="datePicker" k-on-change="onChange()">
</div>
<script>
angular.module("app", [ "kendo.directives" ]).controller("MyCtrl", function($scope) {
$scope.onChange = function() {
alert($scope.datePicker.value());
};
});
</script>
http://docs.telerik.com/kendo-ui/AngularJS/introduction#getting-widget-references

Add event handler to DOM element that is created after a $resource get (AngularJS)

I'm having a hard time enabling a Bootstrap's popover component to my dom elements.
I'm working with AngularJS and on my template, I am using the ng-repeat directive to create a gallery.
<div ng-repeat="phone in phones" >
<a class="thumb" href="#/phones/{{phone.id}}">
<img class="img-responsive phone_image" ng-src="{{phone.image_path}}" data-content="{{phone.text}}" rel="popover" data-placement="right" data-trigger="hover">
</a>
</div>
On my template controller, I'm fetching the phones data from a third party API and than injecting it to the scopes variable "phones", like so:
phoneControllers.controller('PhoneListCtrl', ['$scope', 'Phones',
function ($scope, Cards) {
// Phones is the service that queries the phone data to the API
Phones.query(function(data){
// Got a response, add received to the phones variable
$scope.phones = data;
// for each .card_image element,give it the popover property
$('.phone_image').popover({
'trigger': 'hover'
});
});
}]
);
My problem lies with the $('.phone_image').popover segment. My thought was that by doing it inside the query's callback function it would work, since that's when the ".phone_image" elements are created. However it doesn't.
I seem to be failing to understand exactly in what scope should I assign the .popover property. I know it works because if I do it on the developer tools console, after all page content has been loaded, it works properly. I just don't know where to call it in my code to begin with.
Thanks in advance
It's happening because you are manipulating the DOM inside a controller. You should not do this, as the documentation says:
Do not use controllers to:
Manipulate DOM — Controllers should contain only business logic. Putting any presentation logic into Controllers significantly affects its testability. Angular has databinding for most cases and directives to encapsulate manual DOM manipulation.
In other words, when you use an Angular controller, you're just delegating the DOM manipulation to Angular through $scope databinding.
If you would like to manipulate the DOM, you should rely on directives. In your case, you can create a phonePopover directive like this:
angular
.module('phone', [])
.directive('phonePopover', function() {
return {
restrict: 'A',
replace: false,
scope: {
phoneText: '=phonePopover'
},
link: function(scope, element, attr) {
element.popover({
'trigger': 'hover',
'placement': 'right',
'content': scope.phoneText
});
}
});
And apply it to your element as following:
<img data-phone-popover="{{phone.text}}" class="img-responsive phone_image" ng-src="{{phone.image_path}}">

AngularJs - How should I detach a modal window?

So I've been working with AngularJS for a decent amount of time, yet still have to understand the scenes behind directives.
I am trying to build a directive which attaches a modal window on demand and detaches it from the DOM if not needed anymore.
So i did this:
app.directive('myDirective',function($document){
return{
restrict: 'E',
templateUrl: 'partials/modules/template.html',
link: function($scope,$element){
var body = $document.find('body').eq(0);
$element.remove();
$scope.create = function(){
body.append($element);
};
}
}
});
And found that it will cause the loss of linking between view and controller.
So far, so bad.
But what basic concept am i missing here? What would be a proper way to accomplish this?
I've got a few, messy (and hacky) options in my head, including
using ng-show
setting a CSS class of hide manually
Re-linking the stuff back together after append
They seem weird and simply wrong to me, and i especially don't want to use a style attribute to do this.
I also don't want to use Angular-UI's modal module.
you should definitely re-consider your view about using a style attribute. It is the angular recommended way to go:
'One of the major design goals of AngularJS is to allow application developers to build web apps with little or no direct manipulation of the DOM. In many cases this also leads to a much more declarative style of programming. This allows business logic to be easily unit tested and greatly increases the rate at which you can develop applications.' What is the AngularJS way to show or hide a form element?
I do this kind of thing all the time. Using ng-class and json, it is in my view the simplest way of doing it and the easiest way to test. Here's a rough idea. Also you don't need to append the element to the body, that is the whole purpose of the link phase:
app.directive('myDirective',function($document){
return{
restrict: 'E',
templateUrl: '<div myDirective ng-class="{\'hideClass\':object.hide===true, \'showClass\':object.show===true "></div>',
link: function($scope,$element){
scope.element= {hide:false, show:true}
if(someCondition) {
scope.element.hide = true;
}
if (anotherCondition) {
scope.element.show = true;
}
}
}
});
Then, in your test:
it('should be hidden if...', function () {
angular.mock.inject(function ($compile, $rootScope) {
var scope = $rootScope.$new();
var elem = $compile('<div myDirective></div>')(scope);
// ... some conditional code to manipulate scope.element json, you may need timeout to wait for DOM to load so you can check that the class is present
expect(elem.hasClass('hideClass')).toBe(true);
});
});
The problem is when you call $element.remove() and then body.append($element) in the create method, it is no longer "compiled" angular. This is why the linking is broken. Compiling and appending everytime you want to show isn't the most efficient solution (as you mention above).
Why is it considered hacky to use CSS for display and hiding of the element/modal? This is how I've seen it done in most UI frameworks.
I've put together a jsfiddle of what I believe your problem is (button in template can't call hideMe function) and an example using CSS class.
Ignoring the simplicity of the styles:
.modal.show {
display: block;
}
.modal {
display: none;
position: absolute;
height: 100px;
width: 100px;
margin: 15px auto auto;
border: 1px solid blue;
padding: 5px;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: white;
z-index: 200;
}
Use addClass and removeClass within your scope methods for creating/hiding the modal:
mod.directive("myDirective", function () {
return{
restrict: 'E',
replace: true,
template: '<div class="modal">Hello<button ng-click="hideMe()">×</button></div>',
link: function($scope,$element){
var shade = angular.element('<div class="shade"></div>');
$scope.create = function(){
$element.addClass("show");
$element.after(shade);
};
$scope.hideMe = function () {
$element.removeClass("show");
shade.remove();
}
}
}
});
Hiding/showing elements is one of the thing CSS does really well. You can also get some nice animations and transitions if you wanted using CSS with minimal extra work.

Resources