AngularJS - augmenting dynamic HTML content with user-generated content - angularjs

I'm new to AngularJS and hoping someone can help me get my head round this please!
I'm developing a web e-reader that pulls in pages of HTML content dynamically. So far, I'm doing that with an $http AJAX call and binding it in with 'ng-bind-html-unsafe' (the HTML is our own, simply served from a directory on the same server. I have control over the HTML so I could do this differently if needs be). So each time the user presses previous/next, the controller simply fetches in the previous/next page of HTML and switches that in the model - that works great.
But next I need to augment this dynamic HTML with user additions, e.g. comments and highlights. They need to appear where the user adds them, e.g. a comment would most likely sit underneath a particular paragraph.
With JQuery I guess I would give each of the HTML elements its own ID and associate each bit of user-generated content with a particular ID. Then I could manipulate the DOM myself, for example adding each comment under its associated element.
What's the right approach with AngularJS, since the principle seems to be to avoid direct DOM manipulation altogether?
I could see how it could be done by defining the HTML content as separate elements in the model, having individual JavaScript objects for each paragraph, header, image, etc. But that would basically be defining DOM-like data in JavaScript - and that feels quite wrong and messy...

Use an "ng-include" and dynamically bind a src attribute from the controller. Adding dynamic, user generated content is as adding the binding variables to your html. Following is angular code that implements previous/next buttons that dynamically loads html templates and populates them with the user added comments. At the bottom is a link to a fully functional jsfiddle.
angular.module('app',[]).controller('controller', function($scope){
var change;
change = function(){
$scope.src = $scope.page + '.html';
};
$scope.page = 1;
$scope.items = [];
change();
$scope.submit = function(text){
$scope.items.push(text);
};
$scope.next = function () {
if($scope.page < 3){
$scope.page++;
change();
}
};
$scope.previous = function () {
if($scope.page > 1){
$scope.page--;
change();
}
};
});
http://jsfiddle.net/jwanga/rnL7c/

Related

Retain Tab number in Navigation AngularJs

So I am relatively new to AngularJs and I am trying to figure out the best way for a tabController to remember what tab was previously clicked when switching to a new controller. So the situation would be I have 3 tabs. I click on tab 3 and then I click on something inside of it bringing me to a new controller and HTML template... What is the best way if I hit a "back" Button I created in that controller to remember exactly the state of it being the 3 tab.
I tried using $rootScope and then in each controller setting the tab number and setting the tabcontroller = $rootScope... but that was chaotic and too repetitive, and its not the right way.
This is not about $windoe.back(), this refers to coming up with a way that no matter where the navigation is the tab number is retained.
You can use a factory for this. In angular, a factory is a singleton, meaning only one instance of it exists for the whole project. Thus, by making something with it in one controller (and saving what you've did), you can access your changes in another controller.
angular.module('awesomeApp')
.factory('tabHistoryFactory', function () {
var tabHistory = {
setPrevTab: function(tab) { tabHistory.prevTab = tab; },
getPrevTab: function() { return tabHistory.prevTab; }
};
return tabHistory;
});
Then, in your first controller you'll have to inject this factory and before changing to another tab, just save the tab you're on using tabHistoryFactory.setPrevTab(tab). Then, in your second controller, you can access your previous tab by using tabHistoryFactory.getPrevTab(). Similarly, you can customize the behavior of your tab history by implementing other functions alongside those two.
Good luck!

Angularjs - Charts.js: Same chart element doesn't redraw on other view

I am new to angularjs, trying to create my first directive. I am creating a directive to load Charts.js2.0(beta) into my application.
I have 2 views managed by angular-route, both html view has ng-included a html page that contains only charts-element.
The problem is the first page properly draws the chart, when i go to other view the charts div is loaded but charts is not re-drawn. And now if i go back to first view its blank.
Link to Plunker
What i am doing wrong? Is there any issue with my directive?
Thanks in advance.
There appears to be an issue with the Charts library modifying the existing object on the root scope, and thereby ignoring it forever afterward. I can't really trace down what is doing it, but here's a fix for you: http://plnkr.co/edit/jDQFV62FSeXAQJ6o7jE8
Here is what you had
scope.$watch('config', function(newVal) {
if(angular.isDefined(newVal)) {
if(charts) {
charts.destroy();
}
var ctx = element[0].getContext("2d");
charts = new Chart(ctx, scope.config);
//scope.$emit('create', charts);
}
});
Above, you can see that you're passing scope.config directly into the charts method. That appears to be modifying the data somehow, and since that's passed by reference, you're actually modifying $rootScope.sales.charts. If you copy that object and use it locally like below, you don't have that problem.
Here's how I fixed it.
scope.$watch('config', function(newVal) {
var config = angular.copy(scope.config);
if(angular.isDefined(newVal)) {
if(charts) {
charts.destroy();
}
var ctx = element[0].getContext("2d");
charts = new Chart(ctx, config);
//scope.$emit('create', charts);
}
});
You can see that instead of passing that object directly in, we use angular to make a copy (angular.copy()), and that's the object we pass in.
I think it has relation with the id of the canvas where you are drawing. I've had this problem too amd it was because i was using the same id for the canvas of two graphs in different views. Be sure that those ids are different and that the javasrcipt of each graph is in the controller of each view or in each view itself.
Taking a look at your pluker I see that you are using the same html for the graph and I guess that when angular moves from one of your views to the other thinks that the graph is already drawn. Differentiating two graphs will solve the problem. I don't know of there is any other approach that allows using the same html for the canvas of the graph.
Hope it helps you solve it

AngularJS, single controller or multiple controllers for ng-included views?

I have a dashboard page. Say it is Student dashboard and it has lots of details about the student like his personal details, course details, project details and etc. Inside student.html personal-details.html, course-details.html,project-details.html are included using ng-include.
Student can view those details as well as they can edit it. For this functionality, I have written a single controller StudentController as follows,
(function(){
"use strict";
angular.module('myApp')
.controller('StudentController',StudentController);
StudentController.$inject=['StudentService'];
function StudentController(StudentService){
var vm = this;
vm.studentData = null;
vm.editPersonalDetails = editPersonalDetails();
vm.editCourseDetails = editCourseDetails();
activate();
function activate(){
StudentService.getStudent().then(queryStudentCompleted);
}
function queryStudentCompleted(res){
vm.studentData = res.data;
}
function editPersonalDetails(){
StudentService.editPersonalDetails(vm.studentData);
}
function editCourseDetails(){
StudentService.editCourseDetails(vm.studentData);
}
}
In my real application there are lots of panels to be shown and everything is editable.
1)Should I use different controller for different ng-included pages or single controller is enough(think that more than 7 htmls are included and every panel is editable)? if yes how do the child controllers(say personalDetailsController, courseDetailsController which have edit methods in it) get access to the vm.studentData which is received in the studentController? In Page I can directly show it using ControllerAs syntax, but when I edit it I need to send the edited values to the child controller..
2)Currently studentData has every details that should be shown in the page. Should I keep that in that way or I need to split the data as well into smaller chunks?

ngHide/ngShow using $rootScope in Single Page Application

Reaching out to you all for help yet again for AngularJS. I'm building a SPA where my Layout (master) Page is divided in to three section left navigation bar, center content, right item list bar. My right item list should be hidden when I load my home page, but should be visible in the remaining screens. I've created a $rootScope variable in the homeController and setting the value to 'true' based on the location path and using that variable in the template to set the value to ngHide. When I navigate to a different page as I'm using SPA, my right and left bars won't be loaded again, only my center content do which is a new view. While loading the new view in the controller that is sending the data to the new view's template I'm resetting the $rootScope variable that I've created in homeController to 'false', but my right bar is still not visible. I could see that ng-hidden class is still on though the interpollation has updated the value to 'true'. Any suggestions would be highly appreciated.
markup from layout page:
<aside class="right_bar" ng-hide="{{$root.show}}">
<div class="my-list"><span class="f3">My Cart</span><div class=""><span class="list-count">0</span></div></div><div class="list-item"><ul></ul></div>
</aside>
homeController code:
function getHomeConfigsCtrl($http, $location, $rootScope) {
$rootScope.show = false;
var loc = $location.path();
if (loc === '/Home/Index' || loc ==='')
{
$rootScope.show = true;
}
}
Categories Controller code: This is where I'm resetting my $rootScope variable's value
function getAllCategoryDetails($routeParams,$rootScope) {
$rootScope.show = false;
}
Two things about angular you need to know to make your presented code works:
You don't need to interpolate values using {{}} in ng directives, instead use ng-hide="showCart".
Assuming all your controllers are within the same angular application,
all scopes within the same application inherits from the same root,
i.e whatever you define on the $rootScope will be available to all
child scopes. To access the $rootScope.x from within any view in the application all you have to do is: {{x}} or in your case you're using it inside a directive you can do something like this:
<aside class="right_bar" ng-hide="showCart">
This will look for your current scope, if it has a showCart then it'll use it, otherwise it'll fetch the $rootScope.showCart

AngularJS register controller once

That's what I'm doing. There is application with pages and different controls that may be put on pages by site admin/editor. All pages share one ng-app defined on master page. All controls are supplied with .js files with angular controllers. Let's suppose that I have an image gallery block:
<div ng-controller='imageGalleryCtrl'>
do something amazing here
</div>
<script src='imageGallery.js'></script>
Inside script there is a simple controller registration like:
angular.module('myApp').controller('imageGalleryCtrl', ... );
So. If I have 10 image galleries, I'll execute controller registration 10 times. It looks like this will work, but hell - I don't want it to be so =)
For now I just have all controls' scripts registration on a master page, but I don't like it as well, because if there is no image gallery on a page, I don't want it's script be downloaded during page load.
The question is - is there any proper way to understand if controller have been registered in a module already and thus prevent it from re-registering?
---------------
Well, though I've found no perfect solution, I must admit that the whole idea isn't very good and I won't think about it before my site will grow too big to assemble whole angular app on master page.
You should declare your controller but once. Instead of having one controller per gallery, have your single controller handle all image galleries. The controller should make a request to the REST backend to fetch the images of the desired gallery.
I see that instead of ng-view, you're using the ng-controller directive, indicating that probably you're not using Angular's routing. Try switching to using routes.
Have a look at Angular.js routing tutorial. It shows you how to use the ngRoute module. Then, in the next chapter, the use of $routeParams is described. Via the $routeParams service, you can easily say which gallery should be displayed by providing its ID in the URL; only one controller will be necessary for all your galleries.
If you really must check whether a given controller has been declared, you can iterate through the already declared controllers (and services... and pretty much everything else) by checking the array angular.module("myApp")._invokeQueue. The code would probably look something like this (not tested!):
var isRegistered = function(controllerName)
{
var i, j, queue = angular.module("myApp")._invokeQueue;
for (i = 0, j = queue.length; i < j; ++i) {
if (
queue[i][0] === "$controllerProvider"
&& queue[i][1] === "register"
&& queue[i][2][0] === controllerName
) {
return true;
}
}
return false;
};
Bear in mind however that while this may (or may not) work, it's far from being the correct thing to do. It's touching Angular's internal data that's not meant to be used in your code.

Resources