# AngularJS bookviewer directive written with Typescript and Angular-ui bootstrap

I have been working for weeks on a "BookViewer" AngularJS directive. I would like to write some blog posts on the lessons I learned in writing the directive. Before doing this I would like to ask for some feedback on how I wrote the directive. I ran into a lot trouble, learned a lot, but in the end I am quite happy with the result. I used AngularJS+Typescript+Angular-UI Bootstrap. This is my first attempt to implement a large directive.

The code can be found on GitHub. A live version can be found here.

The visual result of using the directive is as follows:

It is possible to switch between a text and index view by setting the indexmode attribute on the directive which I control from a button in the navigation bar, external from the directive.

I made some decisions where I don't know if these are the best decisions:

Using Typescript in the directive

I could not get the directive working with a Typescript class for the controller and the link function. So I use anonymous functions, and use Typescript only for interface definitions and type-checking. In my link function I need to process the DOM, I do this in an anonymous function in the timeout otherwise the DOM is not ready yet. In the anonymous function I need to call other functions. I could not find another way than defining this function on the scope in the controller function. Is it possible to use functions and variables in an instance of a Typescript class?

directive.link = ($scope: IBookViewerScope,$element, $attrs) : void => {$timeout(() => {
$scope.processDom(); : }, 0); };  The controller function creates a lot of variables and functions in the scope, this was the only way I found to have access to the variables and functions in $timeout callbacks like above. Is this the best approach?

angular.module('directive.bookviewer', [])
.constant('bookviewerConfig', {
})
.controller('bookviewerController',
['$scope', '$element', '$attrs', '$sce', '$location', '$anchorScroll', '$timeout', '$log',
function($scope:IBookViewerScope,$element, $attrs,$sce, \
$location:ng.ILocationService,$anchorScroll, $timeout,$log) : void {
$scope.savedElement =$element; // keep track of element for usage in later code
$scope.savedAttrs =$attrs;  // keep track of attributes for usage in later code

$scope.trustAsHtml = (html:string) : any => { return$sce.trustAsHtml(html);
};

$scope.indexOpenChapterIndex = (chapterIndexId:string) => { ... } : }]);  Handling fixed bar at top of page I use a fixed bar at the top. To compensate for this in jumping around in the book I added anchor tags <a class="anchor" id="..."></a>, and in my directive link function I put on each "anchor" element the following code: $element.css({
display: "block",
position: "relative",
top: offset
});


where offset is -heightOfFixedBar. Problem is that the top of the chapter was still at the wrong offset, so I added a div with the same height as the fixed bar at the top:

<div style="margin: 51px 0 0 0;"/>
<bookviewer booktoc="vm.toc" chaptercontent="vm.chapter"
indexmode="vm.indexmode" offset-element-id="'topnavbar'"
anchorid="vm.anchorid" on-navigate="vm.navigate(chapterid, anchorid)"
on-select="vm.select(text)"/>
</div>


All navigation is done by watching

Initially I started with explicit navigation. I had callback functions connected to links in the html and in those functions I did the scrolling to anchors or callbacks to the function to load the contents of another chapter. I got into a lot of trouble with this, because I also had to respond to changes in the bound parameters of the directive. I ended up with a set of watch functions defined on the scope:

// toggle between text/index view
$scope.$watch('indexmode', (newValue, oldValue) => { ... }

// respond to click on links
$scope.$watch(() => { return $location.hash(); }, () => { ... } // respond to content changes (new chapter content) and anchor changes$scope.$watch('[chaptercontent.Id, anchorid]', function (newValues, oldValues) { ...}  Although I am intending to use only a single instance of the bookviewer per page, it is a bit of a pity to depend on $location.hash() within a directive. Problem is: I could not find another way while keeping the next/prev button of the browser working.

Rendering with dynamic templates

I do all the rendering with templates defined in the directive.templateUrl.

<script id="curChapterIndexTmpl.html" type="text/ng-template">
<li><a ng-click="navigate(paragraph.Id)" href="javascript:void(0)">{{paragraph.Title}}</a></li>
<ng-include ng-repeat="paragraph in paragraph.Paragraphs" src="'curChapterIndexTmpl.html'"></ng-include>
</script>

<script id="otherChapterIndexTmpl.html" type="text/ng-template">
<li><a ng-click="navigate(paragraph.Id)" href="javascript:void(0)">{{paragraph.Title}}</a></li>
</script>

<script id="paragraphTmpl.html" type="text/ng-template">
<a class="anchor" id="{{paragraph.Id}}"></a>
<h4>{{paragraph.Title}}</h4>
<div class="paragraph-text" ng-bind-html="trustAsHtml(paragraph.Content)"></div>
<ng-include ng-repeat="paragraph in paragraph.Paragraphs" src="'paragraphTmpl.html'"></ng-include>
</script>

<div class="bookchapter" ng-hide="indexmode">
<a class="anchor" id="{{chaptercontent.Id}}"></a>
<h3>{{chaptercontent.Title}}</h3>
<div class="chapter-text" ng-bind-html="trustAsHtml(chaptercontent.Content)"></div>
<ng-include ng-repeat="paragraph in chaptercontent.Paragraphs" src="'paragraphTmpl.html'"></ng-include>
</div>

<div id="bookindex" ng-show="indexmode">
<h1>Book index</h1>

<accordion close-others="true">
<accordion-group ng-repeat="tocChapter in booktoc.Chapters" is-open="indexOpenChapterIndex('i-' + tocChapter.Id)">
<a class="anchor" id="i-{{tocChapter.Id}}"></a>
<a ng-click="navigate(tocChapter.Id)" href="javascript:void(0)">{{tocChapter.Title}}</a>
<i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': isopen, 'glyphicon-chevron-right': !isopen}"></i>
<ul ng-if="tocChapter.Id === $parent.chaptercontent.Id"> <ng-include ng-repeat="paragraph in$parent.chaptercontent.Paragraphs" src="'curChapterIndexTmpl.html'"></ng-include>

I must say: I learned my lessons there. I initially worked with self-closing divs (<div ... />). This worked when working with jQuery. When I removed jQuery and depended on the AngularJS JQLite implementation, a lot went wrong. So NEVER use self-closing divs. It isn't even supported in the html5 spec. See this.