Same element id's on different pages of AngularJS app - angularjs

I'm very new to AngularJS, and am working to convert an existing web application written using JQuery and Bootstrap to one using AngularJS and Ionic.
I have 3 pages in my web application, each with a html5 canvas and a button. The canvas layout and styling are exactly the same on each page, however the button draws a different thing to each canvas, e.g. on page 1 draw a dog, page 2 a cat, and page 3 a chicken.
Because of this, I use the same element ID's between the three pages, e.g. '#myCanvas' for the canvas. I have a Javascript object called 'Drawer', which draws the relevant thing to '#myCanvas' when the relevant button is pressed.
When converting to AngularJS, I placed each page into a template, and converted the 'Drawer' object into a factory called DrawFactory. Each page is linked to a different controller that uses DrawFactory.
My problem is that, say for example I'm on page 2 and click the draw cat button, it draws it to the #myCanvas on page 1. Previously this wasn't a problem as each page loaded separately and therefore all ids were unique.
How can I achieve what I want without renaming each of the canvases to (for example) #myCanvas1, #myCanvas2, #myCanvas3- and creating 3 separate DrawFactories that individually draw to one of them?
I'm sure I'm just missing some key AngularJS concept. Thanks.

Without having your code posted, I suspect you may still be using jQuery selectors to find elements (e.g. $('#myCanvas')). You may wish to restructure your code to behave like this at a high level.
<div ng-controller="PageOneCtrl">
<drawing source="animal"></drawing>
<button ng-click="drawAnimal()">Draw</button>
</div>
The controller would be defined similar to the following:
angular.module('app').controller('PageOneCtrl', function ($scope) {
$scope.animal = null;
$scope.drawAnimal = function () {
$scope.animal = 'dog.png';
};
});
And the drawing directive like so:
angular.module('app').directive('drawing', function () {
return {
scope: {
source: '='
},
template: '<canvas width="100" height="100"></canvas>',
link: function (scope, element) {
scope.$watch('source', function (newSource) {
if (!newSource) return;
var context = element.getContext('2d'),
image = new Image(100, 100);
image.src = newSource;
image.onload = function () {
context.drawImage(image, 0, 0);
};
});
}
};
});

Related

OO-layout / formatting (like Wicket) in AngularJS?

I'm a pretty experienced Wicket developer, but curious about AngularJS, so I decided to give it a try. Most things are pretty straightforward when coming from Java (DI etc).
However, I was not able to find a (Object Oriented) strategy for layout of components in my page. In Wicket, you can pack view and behaviour together in a Component, for example a Panel. You can create multiple subclasses for a certain component, and decide which subclass to use in Java.
In my case, I develop a (abstract) Game which has a Board component. Based on the type of game, a different Board will be renderend. Other components, like the title bar, scoring, end-of-game animation etc. will be the same for all games. In Wicket, this would look something like this:
Board b = myGame.newBoardPanel(id);
add(b);
Where the game can provide an abstract method to provide a Board.
I tried looking at views, but it seems hard to combine multiple different dynamic components in them. In my case, next to the Board, I have an AnswerPanel which can also differ from game to game, but for some games, it will be the same.
My next move was to use directives; but it seems AngularJS is not designed to choose layout from the controller, as it is not easy to change (in my case) the templateURL to implement different layout.
TL;DR
What is the Angular way to implement different components in a OO-style?
In angular you would use directives to reuse elements in the game.
Directives and element can have a controller associated with them. The controllers in angular are expected to be functions, which internally are instantiated using the new keyword, so you can use classes. And if you use classes you can use inheritance to share logic between your controllers.
You can modify the contents of a directive HTML node ($element), but you need to load and compile the template manually.
<!-- Use directives to reuse elements -->
<div ng-controller="GameController as gameCtrl">
<title-bar title="gameCtrl.title"></title-bar>
<answer-panel answers="gameCtrl.answers"></answer-panel>
<!-- Pass data into your directives via attributes, from the controller -->
<board type="gameCtrl.boardType"></board>
</div>
ES6
// controllers can be classes, as such they can be extended via mixins
// or OO extend.
class GameController {
constructor($location, gameRepository) {
var ctrl = this;
var params = $location.search();
gameRepository.get(params.id).then(function (game) {
// store the data in the controller instead of the $scope,
// this gives you context to where your data is coming from in
// the templates
ctrl.title = game.title;
ctrl.answers = game.answers
ctrl.boardType = game.boardType;
});
}
}
The title-bar directive would be very simple:
function titleBarDirective() {
return {
restrict: 'E',
// this is a convenience for angular to associate the evaluation of the
// element attribute 'title' to the $scope.title
scope: {
title: '='
},
template: '<span ng-bind="title"></span>'
};
}
For the board, you can include your custom $compiled template, this might not work exactly but I hope it gives you a good starting point.
function boardDirective() {
return {
restrict: 'E',
scope: {
type: '='
},
controller: BoardController,
controllerAs: 'boardCtrl'
};
}
class BoardController {
constructor($scope, $element, $compile, $templateRequest) {
var ctrl = this;
ctrl.$scope = $scope;
ctrl.$element = $element;
ctrl.$compile = $compile;
ctrl.$templateRequest = $templateRequest;
// check for changes to the "type", because in this example the
// type is coming from the gameRepository, so this directive could
// be initialized before a request with the game type comes
// from the server.
$scope.$watch('type', function (newType) {
if (!newType) return;
ctrl._loadBoard(newType);
});
}
_loadBoard(type) {
var ctrl = this;
// manually request the template
ctrl.$templateRequest('/templates/board/' + type + '.html')
.then(function (template) {
// manually compile the template
var boardTpl = ctrl.$compile(template):
// manually add the link of the template with a scope to
// the directive element
ctrl.$element.append(boardTpl(ctrl.$scope));
});
}
}

Angular directive embed in kendo grid does not execute

I'm using kendo ui in my MVC project. So, I have this simple directive that executes when it's not rendered by kendo.
.directive('okok', ['$log', function($log){
return {
link: function (scope, elm) {
$log.log('directive okok!!');
}
};
}])
The directive executes in this line:
<h2 okok>Hello??</h2>
But it does not execute when razor generates the html. Here
#(Html.Kendo().Grid(Model.CoolModel)
.Name("CoolGrid")
.Columns(cols => {
cols.Bound(c => c.StatusDescription).Title("This is my test")
.ClientTemplate("<div okok></div>");
})
/* Mode code :) */
)
Please note the line: .ClientTemplate("<div okok></div>");
Not sure if the output is handle as a string an I have to do something else. Help is appreciated!
I manage this scenario by compiling the element that wraps the grid when the content is bound.
Kendo has an example in its Q&A page where they show how to embed a menu into a grid.cell and the script that enables the submenu has to be executed after the content is render. It kinda lame.
So, I have a function in my controller that compiles an element in the scope.
$scope.rebindElm = function(elm){
$compile(elm)($scope);
}
And in the view a script is executed when kendo.grid.OnContentBound (or something like that)
function bindContent(e){
var elm = angular.element('gridName');
var scope = elm.scope();
if (scope != null) {
scope.rebindElm(elm);
}
}
Yes, it feels like a hack but that's the only way I found to get directives to execute when they are generated with kendo.mvc

A current recommended way to visualize data with AngularJS and D3.js

I develop a web application in which I need to visualize data.
I am using AngularJS for a modular design and separation of view, data and logic.
I am using D3.js to create visualizations of data with SVG elements.
Each of AngularJS and D3.js works very well, but I feel like I'm doing something wrong by using them together in such a decoupled way.
My web application consists of both HTML elements and SVG elements, both are being generated from the same data sets. And yet, I am using two different tools to create and manipulate the UI (Angular to dynamically build the interface and D3 to dynamically build the graphics).
Both the HTML and the SVG elements need to allow interaction with the user.
I'm currently wrapping my SVG graphics with Angular controllers. Inside the controller I am directly building and manipulating the SVGs with D3. When there are many interactions, animations and data changes - this quickly becomes painfully cumbersome and tedious.
I created this simple fiddle as a simplified example of how I currently do things.
See the itemsGraphCtrl controller as an example for using D3 to create graphics from within an Angular controller.
/* ... */
.controller("itemsGraphCtrl", function($scope, dataStore) {
$scope.data = dataStore.data;
var canvas = d3.select(".canvas");
//enter:
canvas.selectAll('circle').data($scope.data)
.enter().append('circle')
.attr('cx', function(d, i) {
return i * 30 + 10;
})
.attr('cy', 50)
.attr('r', 5)
.style('fill', 'white')
.style('stroke', 1)
.on('click', function(d) {
$scope.$apply(function() {
d.selected = !d.selected;
});
});
function update() {
canvas.selectAll('circle').data($scope.data)
.style('fill', function(d) {
return d.selected ? 'black' : 'white';
});
}
$scope.$watch('data', update, true);
});
This hardly seems like the "Angular way" to do things.
Any suggestions / guidelines / improvements / libraries / other solutions will be greatly appreciated.
I would suggest first making your D3 charts into reusable objects, using the pattern suggested by this article or a library like d3.chart.
Once your D3 charts are encapsulated in their own objects, you can wrap them in directives. As you discover how your charts will interact with the rest of your application, you will start to build up an API on the chart objects themselves; for example, the chart object may trigger an event when a circle has been clicked. You can then hook into this API in your directive wrapper. This way, you can integrate your D3 objects into your Angular code, just like you would any other GUI component or plugin.
Once you have your chart, your directive may look like this:
.directive('bubbleChart', function() {
var chart = d3.charts.bubble();
return {
restrict: 'E',
scope: {
data: '=',
emptyMessage: '#'
},
link: function(scope, element, attrs) {
chart.emptyMessage(scope.emptyMessage);
scope.$watch('data', function(data) {
d3.select(element[0])
.datum(data)
.call(chart);
});
}
};
})
You can add additional options to your chart object, or let users set options via attributes.

Creating a new html page from controller

I have an input form page, that when the user submits, it updates that page with some tables of data. If the user likes what they see, they should be able to print a cleaned up (no header nav, footer nav, etc) version of those tables with formatting intact.
I'm creating the tables using bootstrap and angularjs. What I'm trying to do is that when the uset clicks 'print', my angular controller grabs the desired content from the generated page (in a div called 'template') and inserts it into a new html page that I can then open in a new window for printing.
I created a directive that I was hoping would allow me to use $compile to transclude my data:
app.directive('printTemplate', function () {
return {
templateUrl: 'app/partials/printtemplate.html',
replace: true,
transclude: true,
restrict: 'A'
};
});
And in my controller:
$scope.printTemplate = function () {
var page = angular.element(template).contents();
var newpage = $compile("<html><body><div print-template>" + page + "</div></body></html>")($scope);
var win = window.open();
win.document.write(newpage);
//win.print();
};
But this doesn't seem to do what I expect. And it feels like I'm doing it wrong. The idea was that because my data tables require bootstrap script and css references I need to include those in my printtemplate.html file.
Is there an easier/better way to do this, or what am I missing?
Instead of trying to create a whole new page, why not simply use specific css for print media.
As always the w3 is the best place to start http://www.w3.org/TR/CSS2/media.html

Loading with ngInclude into multiple DOM locations

I'm building an app in AngularJS where the user creates some app state, then there are a number of different ways to render it. (For a lame example, let's say they're going to enter three numbers, and then they can choose to render them into a line chart or a pie chart.)
Each rendering type lives in a different HTML file, and I'm letting the user choose a renderer with a <select> which drives the src for an ng-include to load the different renderers. This works fine, so far.
Now, some rendering modes have additional controls; for example, the pie chart might have a "3D" checkbox. Different renderers will have entirely distinct controls. So I want these included files to also create controls for customizing their presentation.
Here's my question: how do I load a single file and let it create its additional control in one spot in the DOM while putting its main content into another spot in the DOM? The additional control cannot appear adjacent to the rendered content in the DOM: it goes in an entirely different location.
Here is an example: http://plnkr.co/edit/1RXVVu?p=preview. I would like, in a.html and b.html, to be able to instantiate their popup controls in the DOM above the <hr>, while having their textual content below the <hr>. (While it would be possible in the example to just put an hr tag in each of a.html and b.html, the real DOM structure is much more complex and doesn't afford that.)
Thanks!
I created a directive to move the element to another container. Here is what I changed from your plunker.
I added jQuery and the directive:
<script type="text/javascript">
(function() {
var app = angular.module('myapp', []);
app.directive('myContainer', [function() {
return {
restrict:'A',
link: function(scope, elem, attrs) {
angular.element(attrs.myContainer).replaceWith(elem);
//angular.element(attrs.myContainer).html(elem);
//angular.element(attrs.myContainer).appendTo(elem);
//etc...
}
};
}]);
})();
</script>
I added a container above the hr:
<div id="#container"></div>
I specified the directive attribute with the container id in a.html and b.html:
<select ng-model="color" ng-options="c for c in ['red', 'blue']" data-my-container="#container"></select>
The container id (or selector) could come from the model dynamically.
I worked the Plunker into a complete solution here: http://tdierks.github.io/angular-element-mover/, code is here: https://github.com/tdierks/angular-element-mover.
The critical directive is:
(function() {
var app = angular.module('controlMover', []);
app.directive('moveTo', [function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.appendTo(angular.element(attrs.moveTo));
scope.$on("$destroy", function() {
elem.remove();
});
}
};
}]);
})();
One note: I had to listen for $destroy events on the scope to remove the moved DOM element when the pane changes. I haven't yet checked to be sure there's no leakage here, but it's a concern.
Random other things I ran into, for the education of others:
JQuery needs to be loaded before Angular.
This app extension with the directive needs to be named on the ng-app directive in the HTML.

Resources