• No results found

angularjs-cookbook.pdf

N/A
N/A
Protected

Academic year: 2021

Share "angularjs-cookbook.pdf"

Copied!
154
0
0

Loading.... (view fulltext now)

Full text

(1)
(2)

AngularJS Cookbook

70 Recipes for AngularJS 1.2

Sascha Brink

This book is for sale athttp://leanpub.com/angularjs-cookbook This version was published on 2014-03-12

This is aLeanpubbook. Leanpub empowers authors and publishers with the Lean Publishing process.Lean Publishingis the act of publishing an in-progress ebook using lightweight tools and many iterations to get reader feedback, pivot until you have the right book and build traction once you do.

(3)

Tweet This Book!

Please help Sascha Brink by spreading the word about this book onTwitter! The suggested hashtag for this book is#angularjs.

Find out what other people are saying about the book by clicking on this link to search for this hashtag on Twitter:

(4)

Contents

Introduction . . . . i

I Directive / View Recipes

. . .

1

1 Create an analog clock with SVG . . . . 2

2 Build a date select . . . . 4

3 How to use AngularJS with a curly braced template engine . . . . 7

4 Use the $compile function . . . . 9

5 Show a confirm box before ng-click is executed . . . 10

6 Create a digital clock . . . 13

7 Enable the save button only on valid form data . . . 15

8 Disable trimming in input fields . . . 17

9 Dynamic service loading with the $injector . . . 18

10 Create a dynamic templateUrl . . . 21

11 Show a box on outstanding http requests (simple) . . . 23

12 Show only initialized data on startup . . . 25

13 Create a markdown live preview . . . 27

14 Table double rows with ng-repeat . . . 29

15 Prevent duplicate warnings in ng-repeat . . . 31

16 Slide right/left with animations . . . 32

(5)

CONTENTS

18 Select box with multiple option . . . 37

19 Select and ng-options . . . 39

20 Make a sortable table . . . 44

21 Make a stacked bar chart . . . 47

22 Prevent event propagation from ng-click . . . 49

23 Submit form on enter key . . . 50

24 Make a syntax highlighter . . . 52

25 Textarea char limit with remaining counter . . . 54

26 Theme support . . . 56

27 Use the inner html of a directive in your template . . . 59

28 Write a blacklist validator . . . 60

29 General purpose uniqueness validator . . . 62

30 Forms with view / edit mode . . . 65

II Controller Recipes

. . .

67

31 All / none / invert for a list of checkboxes . . . 68

32 Controller to controller communication . . . 70

33 Use your view filters in your controller (quick) . . . 72

34 Reset a form . . . 73

35 How to use the same function for multiple watchers . . . 75

III Service Recipes

. . .

76

36 Get current app name (quick) . . . 77

37 Prevent heavy computing operations from making your app sluggish . . . 78

38 How to structure your services . . . 81

(6)

CONTENTS

40 Notification service delayed / sticky . . . 87

41 Why is there a Provider at the end of some services ($route and $routeProvider)? . . 90

42 Replace history path . . . 91

IV Filter Recipes

. . .

92

43 Filter an exact match (quick) . . . 93

44 Get last element(s) in a collection (quick) . . . 95

45 How to highlight a search . . . 97

46 Easy filtering with the filter filter . . . 99

V Promise Recipes

. . . .

101

47 How to cache data with promises . . . 102

48 Convert a 3rd party promise with $q.when . . . 104

49 TODO: How to wait for several async events . . . 106

50 How to transform every callback into a promise . . . 108

VI Testing Recipes

. . . .

110

51 Testing focus directive . . . 111

52 Mocking http requests . . . 112

53 Testing only a subset of tests . . . 114

VII Big Picture Recipes

. . . .

115

54 Redirect to an error page . . . 116

55 Spreading route definitions among modules . . . 117

56 Stop timers before a scope is removed . . . 118

57 What all the extra .js files are doing? . . . 119

(7)

CONTENTS

59 EcmaScript 5 array functions you should know and use . . . 125

60 Execute code at startup . . . 129

61 Finding Bottlenecks with Batarang . . . 130

62 How to use regular urls without the hash . . . 135

63 Report backend errors . . . 137

64 Optional params and wildcards in Router . . . 139

65 Deregister an event listener . . . 141

66 How to use the dot correctly . . . 142

(8)

Introduction

About the author

Sascha Brink is a web technology consultant, freelance writer and author. He’s developing web applications since 12 years. For him AngularJS is the most promising framework on the client side today. On the server side he favors Ruby on Rails. Sascha has published several articles about AngularJS and is a part of the german chapter http://angularjs.de.

(9)
(10)

1 Create an analog clock with SVG

Problem

You want to create a simple animation without using the canvas element.

Solution

This is more an inspirational than a complex example.SVG¹is a vector image format which can be embedded in HTML. So if you’re a little creative, you can do otherwise complex animations with very little code.

Here we use an analog clock as an example for AngularJS in combination with SVG.

It consists of only a circle and 3 lines. The 3 lines are the hands for hour, minute and second. We rotate them with angular and the$intervalservice.

In the HTML, you see an example of how easy it is to embed a SVG. For more information, seehere². 1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 </head>

6 <body ng-controller="MainController">

7 <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">

8 <g>

9 <circle style="stroke: #ccc; fill: #fff;" cx="100" cy="100" r="100"/>

10 <line x1="100" y1="100" x2="100" y2="50"

11 style="stroke-width: 5px; stroke: #333;"

12 ng-attr-transform="rotate({{hourRotation}} 100 100)" />

13 <line x1="100" y1="100" x2="100" y2="20"

14 style="stroke-width: 3px; stroke: #888;"

15 ng-attr-transform="rotate({{minuteRotation}} 100 100)" />

16 <line x1="100" y1="100" x2="100" y2="5"

17 style="stroke-width: 2px; stroke: #bb0000;"

18 ng-attr-transform="rotate({{secondRotation}} 100 100)" />

¹http://en.wikipedia.org/wiki/Scalable_Vector_Graphics

(11)

Create an analog clock with SVG 3 19 </g>

20 </svg>

21 </body>

22 </html>

The application code is easy, too. We inject and use the$intervalservice which runs every second. We then calculate the angle of rotation for each hand.

1 angular.module('cookbookApp', [])

2 .controller('MainController', function($scope, $interval) { 3

4 function calculateRotation() {

5 var now = new Date();

6 $scope.hourRotation = 360 * now.getHours() / 12; 7 $scope.minuteRotation = 360 * now.getMinutes() / 60; 8 $scope.secondRotation = 360 * now.getSeconds() / 60; 9 } 10 $interval(calculateRotation, 1000); 11 calculateRotation(); 12 });

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-svg-clock

(12)

2 Build a date select

Problem

You want to give the user a date selection with month, day and year.

Solution

For this we build a custom directive which takes and date object. The directive uses two-way-databinding to sync the select fields with the attributemodel.

We will call our directive dateselect and will use the following html code for it. We use two directive to show the synchronization between them:

1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 </head>

6 <body ng-controller="MainController">

7 <dateselect model="current"></dateselect><br/>

8 <dateselect model="current"></dateselect><br/>

9

10 {{current | date:'yyyy-MM-dd'}} 11 </body>

12 </html>

(13)

Build a date select 5 1 angular.module('cookbookApp', [])

2 .directive('dateselect', function() {

3 return {

4 restrict: 'E',

5 template:

6 '<select ng-model="date.month" ' +

7 'ng-options="month for month in months"></select>' + 8 '<select ng-model="date.day" ' +

9 'ng-options="day for day in days"></select>' + 10 '<select ng-model="date.year" ' +

11 'ng-options="year for year in years"></select>',

12 scope : {

13 model: '='

14 },

15 controller: function($scope) {

16 var i; 17 $scope.date = {}; 18 19 $scope.days = []; 20 for (i = 1; i <= 31; i++) { $scope.days.push(i); } 21 22 $scope.months = []; 23 for (i = 1; i <= 12; i++) { $scope.months.push(i); } 24 25 $scope.years = [];

26 for (i = 1980; i <= (new Date().getFullYear()); i++) {

27 $scope.years.push(i);

28 }

29

30 $scope.$watch('model', function(newDate) { 31 $scope.date.month = newDate.getMonth()+1; 32 $scope.date.day = newDate.getDate(); 33 $scope.date.year = newDate.getFullYear();

34 }, true);

35

36 $scope.$watch('date', function(newDate) { 37 $scope.model.setDate(newDate.day); 38 $scope.model.setMonth(newDate.month-1); 39 $scope.model.setFullYear(newDate.year); 40 }, true); 41 } 42 };

(14)

Build a date select 6

43 })

44 .controller('MainController', function($scope) { 45 $scope.current = new Date();

46 });

The directive works as follows:

We setrestrictthe E (for element), to allow dateselect as tag. We use an isolated scope withmodel as our communicator to the outside world. In the template we simply draw three select fields for month, day and year. We generate the necessary options in the controller.

Because we can fully rely on the template, we don’t need the link function here and just use the controller.

The controller is the part where the whole work is done. Here we have three loops for days, months and years for the select boxes. Furthermore we initialize an objectdate, to hold the selected month, day and year.

Additionally we have to watches. One to reflect a model change from the outside and one to reflect a change from the select boxes.

Be careful to set thetrueas second parameter for$scope.$watch('...', function() {}, true). This signals to do a deep watch. Otherwise the updating won’t work all of the time.

The two watches are very similar. If themodelattribute changes, we update the select fields. If we change one of the select boxes, we update the model. Just just have to be careful with the month. The month is inconsistent. While days begin with 1, months begin with 0. This means you have to add or subtract 1.

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-birthdate-select

(15)

3 How to use AngularJS with a curly

braced template engine

Problem

You want to change the double curly braces {{ from AngularJS because they’re conflicting with your template engine on the backend.

Solution

You can indeed change the curly braces to e.g.[[. In AngularJS, the$interpolateprovider does the template interpretation and has an option for changing the symbols.

To do so, you can inject the$interpolateProviderin the config block of your module. There you can setstartSymbolandendSymbolas demonstrated below:

1 angular.module('cookbookApp', [])

2 .config(function($interpolateProvider){ 3 $interpolateProvider.startSymbol('[[');

4 $interpolateProvider.endSymbol(']]');

5 });

If you split your application into different modules, you have to configure the the alternative syntax for every module!

Tip 1: $interpolate stand alone

You can also use the interpolate provider for your own templating purposes. Just inject $interpolate and use:

1 $interpolate('Hello {{name}}!')({name: 'World'}); // => 'Hello World!'

Tip 2: Skipping interpolation

(16)

How to use AngularJS with a curly braced template engine 8 1 <div ng-non-bindable>

2 ...

(17)

4 Use the $compile function

Problem

You created an html string with some AngularJS directives inside your link function. As you attach it to the DOM, you just get exactly what you wrote as output but you expected AngularJS to compile it for you.

Solution

If you you use AngularJS directive in your normal html, AngularJS will compile it for you when the site is rendered. If you create a string inside a link function of a directive, you have to do this manually. You can do this by injecting the$compileservice. You can use $compilewith either a stringor an angular.element. This is returning a linking function which is called with a scope. This again returns an element which the can be inserted into the DOM.

With a string:

1 newElement = $compile(myString)(scope) With an element:

1 newElement = $compile(angular.element)(scope) Returns anangular.element, not a string!

What if don’t have a scope to compile it against?

Usually you would use$compileinside a link function of a directive and there you would have a scope. If for some reason you use it somewhere else, e.g. inside a service, you could of pass the scope to the function from outside. If you just need a scope, you can also just use the root scope. Inject $rootScopeand you’re ready to go.

(18)

5 Show a confirm box before ng-click

is executed

Problem

For an delete button you don’t want to execute a ng-click immediately but first show a confirm box

Solution

We create a new directive which is called confirmed-click instead of ng-click. This directive works the same (you can also use$eventin it) but with a confirm box before the click is executed. Here is an example:

1 <button confirmed-click="removeTask($index)">

The full view we use is a simple list with tasks you can delete. Before the deletion comes a confirm box:

1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 <link rel="stylesheet" ng-href="style.css"/>

6 </head>

7 <body ng-controller="MainController">

8 <table>

9 <tr ng-repeat="task in tasks">

10 <td>{{task}}</td>

11 <td>

12 <button confirmed-click="removeTask($index)">Delete</button>

13 </td>

14 </tr>

15 </table>

16 </body>

(19)

Show a confirm box before ng-click is executed 11 For this to works, we bind the element to the click event and create a new box and disable the butotn when its clicked. The box itself has a new scope with two functionsok()andcancel(). If we click ok()we trigger the original click event. Oncancel()we close the box an reenable the button.

1 angular.module('cookbookApp', [])

2 .directive('confirmedClick', function($parse, $q, $compile, $rootScope) { 3 var box = '<div class="box"><div>Really?</div> ' +

4 '<button ng-click="close($event, true)">OK</button>' + 5 '<button ng-click="close($event)">Cancel</button>' + 6 '</div>';

7 return {

8 link: function(scope, element, attrs) {

9 var fn = $parse(attrs.confirmedClick); 10 element.on('click', function() { 11 var boxScope = $rootScope.$new();

12 var boxElement = $compile(box)(boxScope); 13

14 element.attr('disabled', 'disabled'); 15 element.append(boxElement);

16

17 boxScope.close = function(event, execute) {

18 event.stopPropagation();

19 element.removeAttr('disabled');

20 boxElement.remove();

21 if (execute) {

22 fn(scope, {$event: event});

23 } 24 }; 25 }); 26 } 27 }; 28 })

29 .controller('MainController', function($scope) { 30 $scope.tasks = ['Tidy up', 'Wash the dishes']; 31 $scope.removeTask = function(index){

32 $scope.tasks.splice(index, 1);

33 };

34 });

For the style we give the directive element a relative position, so that we can place the box we show absolute to it:

(20)

Show a confirm box before ng-click is executed 12 1 button[confirmed-click] {

2 position: relative;

3 } 4

5 button[confirmed-click] .box {

6 position: absolute; 7 border: 1px solid #ccc; 8 width: 100px; 9 background-color: #f9f9f9; 10 top: 0; 11 right: -100px; 12 }

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-confirm-box

(21)

6 Create a digital clock

Problem

You want to create a directive which shows the current time.

Solution

The solution is a little directive with a timer. For displaying the date, we use the date filter which does the formatting for us (<div ng-bind="now | date:\'HH:mm:ss\'"></div>). The updating is done through a timer with$interval. The important thing is to remove the timer when the directive is removed from the DOM. This is done by listening to the$destroyevent which is called when the removal of the directive happens.

$interval is first available since Angular 1.2.

1 angular.module('cookbookApp', [])

2 .directive('digitalClock', function($interval) {

3 return {

4 restrict: 'E', 5 scope: {},

6 template: '<div ng-bind="now | date:\'HH:mm:ss\'"></div>',

7 link: function (scope) {

8 scope.now = new Date();

9 var clockTimer = $interval(function() { 10 scope.now = new Date();

11 }, 1000);

12

13 scope.$on('$destroy', function(){ 14 $interval.cancel(clockTimer);

15 });

16 }

17 };

(22)

Create a digital clock 14 1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 </head> 6 <body> 7 <digital-clock /> 8 </body> 9 </html>

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-digital-clock

(23)

7 Enable the save button only on valid

form data

Problem

You have a form with a save button and you want to only enable it when the form data use typed is valid.

Solution

Not only single form fields but the whole form has its own states to test its validity. You can test for $dirtyand$valid.

For example: If we a use a form with the nameform, we could only enable the submit button if the user changes one of the fields (dirty) and if then all fields are valid.

1 <button ng-disabled="!(form.$dirty && form.$valid)">Save</button>

Be sure to give the form a name. Additionally, setnovalidateas attribute and disable the browser’s own validations.

Here’s the full working example: 1 <html ng-app>

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <link rel="stylesheet" ng-href="style.css"/>

5 </head>

6 <body>

7 <form name="form" novalidate>

8 <label>Login:</label> <input type="text" ng-model="login" required/>

9 <button ng-disabled="!(form.$dirty && form.$valid)">Save</button>

10 </form>

11 </body>

(24)

Enable the save button only on valid form data 16 1 input.ng-invalid.ng-dirty { 2 background-color: #ffc4d0; 3 } 4 5 input.ng-valid.ng-dirty { 6 background-color: #d8ffd0; 7 }

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-disable-save-button

(25)

8 Disable trimming in input fields

Problem

You need the data from a textarea as it is on saving without the automatic trimming.

Solution

AngularJS makes good assumptions as default. For example, automatic trimming. If you don’t want it, you can disable it with:

(26)

9 Dynamic service loading with the

$injector

Problem

You have a lot of select boxes whose items are loaded from the server. You don’t want to repeat yourself here. Instead of injecting the needed services into the controller, you just want the directive to handle it automatically.

Solution

Instead of letting the injector automatically inject the services by name in the directive definition, you trigger the injection process manually. To do this, you use the$injectorinstance.

In this example, we create a directive with the following attributes: • model: Translates to ng-model in the select field

• resource: The service name and the function of the service you want to use separated by a dot. • resource-id: The key in the returning object you want to use as options value.

• resource-label: The key in the returning object you want to use as options label. The directive with looks like this:

1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 </head>

6 <body ng-controller="MainController">

7 <dynamic-select model="personId" resource="People.getList"

8 resource-id="id" resource-label="name" />

9 </body>

(27)

Dynamic service loading with the $injector 19 The directive dynamicSelect itself is not that complicated. We have an isolated scope where we model, resourceId and resourceLabel. resource is directly read through the attrs function parameters because we don’t allow to dynamically change the service and we’re not needing it in the template. (See recipe use the scope right TODO).

In the link function, we split the string from resource into two parts and write them into an object for better readability. Then we just use$injector.getto return the service as object. We then use the function as read intoparams.fn. We use.thenhere directly because we assume that our service function returns a promise.

1 angular.module('cookbookApp', []) 2 .factory('People', function($http) {

3 return {

4 getList: function() { return $http.get('person.json'); }

5 };

6 })

7 .directive('dynamicSelect', function($injector) {

8 return { 9 restrict : 'E', 10 scope: { 11 model: '=', 12 resourceId: '@', 13 resourceLabel: '@' 14 },

15 template: '<select ng-model="model" ng-options="item[resourceId] ' + 16 'as item[resourceLabel] for item in items" />',

17 link: function(scope, element, attrs) {

18 var temp = attrs.resource.split('.');

19 var params = { name: temp[0], fn: temp[1] }; 20

21 var service = $injector.get(params.name); 22

23 service[params.fn]().then(function(serviceResponse) { 24 scope.items = serviceResponse.data;

25 });

26 }

27 };

28 })

29 .controller('MainController', function($scope) { 30 $scope.personId = 2;

(28)

Dynamic service loading with the $injector 20 This is example is greatly simplified. You would of course need error checking if you’re consuming services other than $http.

Just for a complete example, the used JSON file. 1 [

2 { "id": 1, "name": "John" }, 3 { "id": 2, "name": "Bill" }, 4 { "id": 3, "name": "Phil" } 5 ]

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-dynamic-service-loading

(29)

10 Create a dynamic templateUrl

Problem

You have several html templates which you want to use dynamically inside your directive.

Solution

Inside a directive,templateUrlcannot only take a string but also a function. The function has the element and the attributes as parameters.

In the following example, we dynamically fill a textarea with two different templates which are loaded dynamically. We write a directive which is named ‘prefill’. This takes the name of the template as argument.

1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 </head>

6 <body>

7 <textarea prefill="filler1"></textarea>

8 <textarea prefill="filler2"></textarea>

9 </body>

10 </html>

The directive itself consists only of thetemplateUrlwith a function. Here we just take the argument from theprefillattribute and append.htmlto it to create the url.

(30)

Create a dynamic templateUrl 22 1 angular.module('cookbookApp', [])

2 .directive('prefill', function() {

3 return {

4 templateUrl: function(element, attrs) {

5 return attrs.prefill + '.html';

6 }

7 };

8 });

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-dynamic-template-url

(31)

11 Show a box on outstanding http

requests (simple)

Problem

While you sending http request, you want to show a box or a loading spinner

Solution

As the solution here, we write a directive which shows an element if a condition is true. This could be a simple div with a string or a loading spinner. It doesn’t matter where you place it in your DOM because the scope is isolated.

The way it works is, that it injects the $httpbecause we need to know the count of the pending requests. After a request is started, the count changes and so does$http.pendingRequests.length. When it does, we get the result of http.pendingRequests.length > 0in the variable value. This is either true or false. We write the result in $scope.waitingand our div is visible or hidden depending of the state.

1 .directive('waitingForRequest', function($http) { 2 var pendingRequests = function() {

3 return $http.pendingRequests.length > 0;

4 };

5 return {

6 restrict: 'E', 7 scope: {},

8 template: '<div ng-show="waiting">Waiting for request to finish...</div>', 9 controller: function($scope) {

10 $scope.$watch(pendingRequests, function(value) {

11 console.log('Pending requests: '+ $http.pendingRequests.length); 12 $scope.waiting = value;

13 });

14 }

15 };

(32)

Show a box on outstanding http requests (simple) 24

Discussion

This is a really simple but not the best solution. But for most applications it should be good enough. There are to issues here:

The watcher is executed every time an apply() is called. This means$http.pendingRequests.length > 0is called a lot of times. But for most applications it’s does not mean a real performance hit.

(33)

12 Show only initialized data on

startup

Problem

You start your not so small app and while AngularJS is booting up, your whole screen is crowded with curly braces.

Solution

First, the why. This situation can appear when open yourindex.html. The browser has to download all the javascript and css files and during this time, AngularJS hasn’t parsed the page yet. So all over the page, you see curly braces for a short moment which are replaced when AngularJS is initialized. To prevent this, you can use 2 directives:ng-cloakandng-bind.

ng-clock

ng-cloak is a directive which removes itself when the application is initialized. If used with the following CSS, all elements are hidden during the initialization process.

1 [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {

2 display: none !important;

3 }

ng-bind

If you don’t want to hide some elements completely because then the layout is broken, you can also useng-bind.ng-bindis similar to{{}}but replaces everything which is inside the tag where it is defined. If you would use curly braces and define a counter like this:

1 <span>{{counter}}</span>

(34)

Show only initialized data on startup 26 1 <span ng-bind="counter">?</span>

This has the advantage that a question mark is displayed as long the app isn’t fully initialized. When the app is ready, the question mark is replaced with the real data.

ng-bind-template

If you want to useng-bindbut have more than one expression, you can useng-bind-template. It’s likeng-bindbut can contain multiple expressions.

(35)

13 Create a markdown live preview

Problem

You’re using markdown somewhere in your application. Users who edit the markdown templates should see a live preview of the resulting html.

Solution

In this example, we implement the directive with the Showdown¹ library which is a popular markdown interpreter.

We create a directive ‘markdownPreview’ which has an isolated scope and takes a model as input attribute. As we create a new tag<markdown-preview>, we have to restrict it to element (E). In the link function, we’re watching for a change of the model attribute and convert the markdown input to an html output. The result is pasted into the elements’ inner html.

1 angular.module('cookbookApp', [])

2 .directive('markdownPreview', function() { 3 var converter = new Showdown.converter();

4 return {

5 restrict: 'E',

6 scope : {

7 model: '='

8 },

9 link: function(scope, element) {

10 scope.$watch('model', function(markdownText){

11 var resultHtml = converter.makeHtml(markdownText || ''); 12 element.html(resultHtml); 13 }); 14 } 15 }; 16 }); ¹https://github.com/coreyti/showdown

(36)

Create a markdown live preview 28 1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="showdown/showdown.js"></script>

5 <script src="application.js"></script>

6 </head>

7 <body>

8 <textarea ng-model="markdown" cols="60" rows="10"></textarea>

9 <markdown-preview model="markdown"></markdown-preview>

10 </body>

11 </html>

Discussion

Oftentimes, there are more solutions to a problem. If you want, you could also implement the markdown as a filter instead of directive. But be aware that you have to include the ngSanitize module and useng-bind-html. Otherwise, your resulting html will be escaped.

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-markdown-live-preview

(37)

14 Table double rows with ng-repeat

Problem

You want to to repeat multiple elements which are siblings. Usually ng-repeat only repeat the current element and its children. See what’s new in AngularJS 1.2.

Solution

Since AngularJS 1.2 there’s a new optional syntax forng-repeat. With it you can define a start and an end element for the repeater. The only restriction is that the element have to be siblings.

1 <table border="1">

2 <tr ng-repeat-start="user in users">

3 <td colspan="2">{{user.name}}</td> 4 </tr> 5 <tr ng-repeat-end> 6 <td>{{user.age}}</td> 7 <td>{{user.gender}}</td> 8 </tr> 9 </table> The source: 1 <html ng-app="cookbookApp"> 2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 </head>

6 <body ng-controller="MainController">

7 <table border="1">

8 <tr ng-repeat-start="user in users">

9 <td colspan="2">{{user.name}}</td>

10 </tr>

11 <tr ng-repeat-end>

12 <td>{{user.age}}</td>

(38)

Table double rows with ng-repeat 30 14 </tr> 15 </table> 16 </body> 17 </html> <<(code/directives-ng-repeat-double-rows/style.css) 1 [

2 { "name": "Bill", "age": 42, "gender": "male" }, 3 { "name": "John", "age": 52, "gender": "male" }, 4 { "name": "Anne", "age": 19, "gender": "female" }, 5 { "name": "Phil", "age": 21, "gender": "male" }, 6 { "name": "Mary", "age": 23, "gender": "female" } 7 ]

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-ng-repeat-double-rows

(39)

15 Prevent duplicate warnings in

ng-repeat

Problem

If you useng-repeatwith duplicates in an array, you get the error message “Duplicates in a repeater are not allowed”.

Solution

This happens because AngularJS exspects every element to have an unique identifier. This is for tracking the insertion, deletion and moving of element. To change the identifier for the element you can use thetrack bysyntax.

If the the following snippet throws an error: 1 <li ng-repeat="item in [4,4,4]">

You can enforce artifical uniqueness by using the index of the current element in the repeater: 1 <li ng-repeat="item in [4,4,4] track by $index">

(40)

16 Slide right/left with animations

Problem

You want to add slide effects for left and right to your ng-view div.

Solution

With a little trick, ng-animate will do all the heavy lifting for us. The idea is to set a variable for the direction before the url is changed. The animation direction depends on a css class which is altered when the direction changes.

Step 1: The styling

The first thing we need is some styling for the slide effects and a container to wrap them: 1 .container {

2 position:relative; overflow:hidden;

3 border: 1px solid black; height:100px;

4 } 5

6 .slide-left, .slide-right {

7 position: absolute; top: 0; left: 0; right: 0; bottom: 0;

8 width: 100%; padding: 10px;

9 } 10

11 .slide-left.ng-enter, .slide-left.ng-leave, 12 .slide-right.ng-enter, .slide-right.ng-leave {

13 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) .5s; 14 -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) .5s; 15 -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) .5s; 16 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) .5s; 17 }

18

19 .slide-left.ng-enter { left: 100%; }

20 .slide-left.ng-enter.ng-enter-active { left: 0; } 21 .slide-left.ng-leave { left: 0; }

(41)

Slide right/left with animations 33 22 .slide-left.ng-leave.ng-leave-active { left: -100%; } 23 24 .slide-right.ng-enter { left: -100%; } 25 .slide-right.ng-enter.ng-enter-active { left: 0; } 26 .slide-right.ng-leave { left: 0; } 27 .slide-right.ng-leave.ng-leave-active { left: 100%; }

The container must haveposition: absolute or relative. The slide classes must haveposition: absolute. For left and right sliding, we defineslide-leftandslide-rightwith additional classes forng-animate¹.

Step 2: The code

For demo purposes, we define two sample routes with direct template declaration with the $routeProvider. For the url change, we define two functions, moveLeft and moveRight, in a controller for the left and right slide. With $location service, we can change the url from a controller.

1 angular.module('cookbookApp', ['ngRoute', 'ngAnimate']) 2 .config(function($routeProvider) {

3 $routeProvider

4 .when('/1', { template: '<h1>first page</h1>' }) 5 .when('/2', { template: '<h1>second page</h1>' }) 6 .otherwise({redirectTo: '/1' });

7 })

8 .controller('MainController', function($scope, $location) { 9 $scope.moveLeft = function(href) {

10 $scope.direction = 'left'; 11 $location.path(href);

12 };

13

14 $scope.moveRight = function(href) { 15 $scope.direction = 'right';

16 $location.path(href);

17 };

18 });

Since AngularJS 1.2, ngRoute is a module on its own. Be sure to include ngRoute and ngAnimate!

(42)

Slide right/left with animations 34

Step 3: The markup

In the app code itself we use the defined methods for the links withng-click="moveRight('/1')".

Depending on the direction variable,ng-viewgets the right animation withng-class="'slide-'+direction". 1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="../vendor/angular-route.js"></script>

5 <script src="../vendor/angular-animate.js"></script>

6 <script src="application.js"></script>

7 <link rel="stylesheet" href="style.css"/>

8 </head>

9 <body ng-controller="MainController">

10 <a href ng-click="moveRight('/1')">Page 1</a>

11 <a href ng-click="moveLeft('/2')">Page 2</a>

12

13 <div class="container">

14 <div ng-view ng-class="'slide-'+direction"></div>

15 </div>

16 </body>

17 </html>

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-ng-view-slide-animation

(43)

17 Pass a function to a directive with

an isolated scope (&)

Problem

You have a directive with an isolated scope want to call a function in the outside scope. Or you to understand what the&in the isolated scope definition is doing.

Solution

While isolated scopes with@and=are easy understood by most people, a lot of them have a problem to understand what exactly the&is doing.

This is a basic concept but we repeat it here, because it is so often misunderstood.

As a general information. Every declaration of the scope isolation refers to an attribute of the element of the directive.

An declaration like

1 directive('colorpicker', function() {

2 ... 3 scope: { 4 a: '@', 5 b: '=', 6 c: '&' 7 }

means the that you can use a, b and c inside the directive. The connection to the outer world is declared through attributes on the directive element. So the element would look like<colorpicker a="4" b="myVar" c="myCallback()">. This is important. You don’t connect to the parent scope directly, but only through attribute declaration.

So, what is the

&

doing?

If you have the following situation:

(44)

Pass a function to a directive with an isolated scope (&) 36 1 +- controller scope

2 +--- directive scope

The directive is isolated and nested under the controller scope, you can execute a function on the controller scope from the directive scope.

Why is this important

This is important because you’re free to choose your function name and which parameters you pass. This means you could use<colorpicker c="myCallback(paramA, paramB)">in one controller and <colorpicker c="mySuperCallback (paramB, paramA)">in another.

How do I use it?

So, you want to call a function like this in the controller 1 $scope.myCallback = function(first, second) { ... }

The directive could have a execution of the function like this: 1 scope.c({ paramA: 123, paramB: 'xzy');

The you would have to use the directive in the template like this 1 `<colorpicker c="myCallback(paramA, paramB)">`

This means, every time the function is trigger inside the directive, it is translated to: 1 myCallback(123, 'xzy')

(45)

18 Select box with multiple option

Problem

You want to use a select box with the optionmultiple.

Solution

Theng-options directive of AngularJS does work well with themultipleoption. You don’t need an extra configuration for this. If multipleis present as an attribute,ng-modelcontains an array instead of a simple type.

The binding here is also bidirectional. Below is an example of how to use it. As you can see, you can set initial values just by declaring an array.

1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 </head>

6 <body ng-controller="MainController">

7 <select multiple ng-model="selected"

8 ng-options="person.id as person.name for person in people">

9 </select>

10 {{ selected }} 11 </body>

(46)

Select box with multiple option 38 1 angular.module('cookbookApp', [])

2 .controller('MainController', function($scope) { 3 $scope.people = [ 4 { id:1, name:'John' }, 5 { id:2, name:'Bill' }, 6 { id:3, name:'Phil' } 7 ]; 8 9 $scope.selected = [1,2]; 10 });

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-select-multiple

(47)

19 Select and ng-options

Problem

You struggle with the instructions forng-optionsgiven in the api.

Solution

Here we give examples for every way you can useng-options. Source code ((((here)))))

Array data sources

For all the examples, we use the following array: 1 users = [

2 { id: '800', name: 'Bill Mayer', role: 'Admin' }, 3 { id: '801', name: 'Anne Black', role: 'User' } 4 ];

What you’ll see in all examples is thatng-optionssets the values for arrays on it’s own. You can’t change the value of the options.ng-optionstranslates them automatically.

label for value in array Select tag withng-options:

1 <select ng-model="arr1" ng-options="user.name for user in users"> The resulting options:

1 <option value="0" selected="selected">Bill Mayer</option>

2 <option value="1">Anne Black</option>

(48)

Select and ng-options 40 1 {"id":"800","name":"Bill Mayer","role":"Admin"}

select as label for value in array Select tag withng-options:

1 <select ng-model="arr2" ng-options="user.id as user.name for user in users"> The resulting options:

1 <option value="0" selected="selected">Bill Mayer</option>

2 <option value="1">Anne Black</option>

The contents of ng-model: 1 800

label group by group for value in array Select tag withng-options:

1 <select ng-model="arr3"

2 ng-options="user.name group by user.role for user in users"> The resulting options:

1 <optgroup label="Admin">

2 <option value="0">Bill Mayer</option>

3 </optgroup>

4 <optgroup label="User">

5 <option value="1">Anne Black</option>

6 </optgroup>

The contents of ng-model:

1 {"id":"800","name":"Bill Mayer","role":"Admin"}

select as label group by group for value in array Select tag withng-options:

(49)

Select and ng-options 41 1 <select ng-model="arr4"

2 ng-options="user.id as user.name group by user.role for user in

3 users">

The resulting options: 1 <optgroup label="Admin">

2 <option value="0">Bill Mayer</option>

3 </optgroup>

4 <optgroup label="User">

5 <option value="1">Anne Black</option>

6 </optgroup>

The contents of ng-model: 1 800

Object data sources

For all the examples we use the following object: 1 roles = {

2 150: { name: 'Admin', rights: 'Read+Write' }, 3 151: { name: 'User', rights: 'Read' }

4 };

label for (key, value) in object Select tag withng-options:

1 <select ng-model="obj1" ng-options="obj.name for (roleId, obj) in roles"> The resulting options:

1 <option value="150" selected="selected">Admin</option>

2 <option value="151">User</option>

(50)

Select and ng-options 42 1 {"name":"Admin","rights":"Read+Write"}

select as label for (key , value) in object Select tag withng-options:

1 <select ng-model="obj2" ng-options="id as obj.name for (id, obj) in roles"> The resulting options:

1 <option value="150" selected="selected">Admin</option>

2 <option value="151">User</option>

The contents of ng-model: 1 150

label group by group for (key, value) in object Select tag withng-options:

1 <select ng-model="obj3"

2 ng-options="obj.name group by obj.rights for (id, obj) in roles"> The resulting options:

1 <optgroup label="Read+Write">

2 <option value="150">Admin</option>

3 </optgroup>

4 <optgroup label="Read">

5 <option value="151">User</option>

6 </optgroup>

The contents of ng-model:

1 {"name":"Admin","rights":"Read+Write"}

select as label group by group for (key, value) in object Select tag withng-options:

(51)

Select and ng-options 43 1 <select ng-model="obj4"

2 ng-options="id as obj.name group by obj.rights for (id, obj) in roles"> The resulting options:

1 <optgroup label="Read+Write">

2 <option value="150">Admin</option>

3 </optgroup>

4 <optgroup label="Read">

5 <option value="151">User</option>

6 </optgroup>

The contents of ng-model: 1 150

Custom empty option

If you don’t want to show a blank field but your own custom label, you can just insert an option tag with a blank value.

1 <select ng-model="choose" ng-options="user.name for user in users">

2 <option value="">- Choose user -</option>

3 </select><br>

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-select-ng-options

(52)

20 Make a sortable table

Problem

You want to make your table sortable. The element in the header which the table is ordered by is highlighted. If you click again on the active element the sort order is reversed.

Solution

For this we write a directivethSortablewhich you can later use like this: 1 <th th-sortable="sort" th-column="name" th-default>Name</th>

The directive wraps the contents of an th tag into a link. The link gets a classactive if it is the current selected sort order. If the element is already active and you click the link again the order is reversed.

We isolate the scope of the directive and set two variables: 1 angular.module('cookbookApp', [])

2 .directive('thSortable', function() {

3 return {

4 transclude: true,

5 template: '<a href ng-click="changeColumn()" ng-class="{' + 6 'active: sort.column == column, ' +

7 'asc: !sort.reverse, ' + 8 'desc: sort.reverse' + 9 '}">' + 10 '<span ng-transclude></span></a>', 11 scope : { 12 sort: '=thSortable', 13 column: '@thColumn' 14 },

15 controller: function($scope, $attrs) { 16 $scope.sort = $scope.sort || {}; 17

(53)

Make a sortable table 45 19 $scope.sort.column = 'name'; 20 } 21 22 $scope.changeColumn = function() { 23 if ($scope.sort.column === $scope.column) { 24 $scope.sort.reverse = !$scope.sort.reverse; 25 } else {

26 $scope.sort = { column: $scope.column, reverse: false };

27 }

28 };

29 }

30 };

31 })

32 .controller('MainController', function($scope, $http) { 33 $http.get('users.json').then(function(usersResponse) { 34 $scope.users = usersResponse.data;

35 });

36 });

1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 <link rel="stylesheet" ng-href="style.css"/>

6 </head>

7 <body ng-controller="MainController">

8 <table>

9 <tr>

10 <th th-sortable="sort" th-column="name">Name</th>

11 <th th-sortable="sort" th-column="age" >Age</th>

12 <th th-sortable="sort" th-column="gender" th-default>Gender</th>

13 </tr>

14 <tr ng-repeat="user in users | orderBy:sort.column:sort.reverse">

15 <td ng-bind="user.name"></td> 16 <td ng-bind="user.age"></td> 17 <td ng-bind="user.gender"></td> 18 </tr> 19 </table> 20 </body> 21 </html>

We use stylesheets to highlight the current column and show the order. Because we set .asc or .desc as class for the direction, we can use the the css content attribute and append and arrow.

(54)

Make a sortable table 46 1 th a { 2 display: block; 3 text-decoration: none; 4 } 5 6 th a.active { 7 background-color: #ccc; 8 } 9 10 th a.active.asc:after { 11 content: "\2193"; 12 } 13 14 th a.active.desc:after { 15 content: "\2191"; 16 }

Just for demo purposes the json file with the demo data. 1 [

2 { "name": "Bill", "age": 42, "gender": "male" }, 3 { "name": "John", "age": 52, "gender": "male" }, 4 { "name": "Anne", "age": 19, "gender": "female" }, 5 { "name": "Phil", "age": 21, "gender": "male" }, 6 { "name": "Mary", "age": 23, "gender": "female" } 7 ]

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-sortable-table

(55)

21 Make a stacked bar chart

Problem

You want to create a chart with stack bars.

Solution

We solve it through a directive which takes an array of percentages and draws the chart.

For the bars, we define an array of integers in the controller. In the directive, it’s enough to just use a template with anng-repeat. There’s one trick here.ng-repeatdoesn’t allow duplicates. If we would just useng-repeat="bar in bars"and use an array like[20,20,40], AngularJS would throw an error likeDuplicates in a repeater are not allowed. We solve this by usingtrack by $index" and force uniqueness.

1 .stacked-bar-chart { 2 overflow: hidden; 3 margin: 10px 0; 4 height: 20px; 5 border: 1px solid #ccc; 6 background-color: #f9f9f9; 7 } 8

9 .stacked-bar { float: left; height: 100%; } 10 .stacked-bar-0 { background-color: #bb0000; } 11 .stacked-bar-1 { background-color: #00bb00; } 12 .stacked-bar-2 { background-color: #0000bb; }

(56)

Make a stacked bar chart 48 1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 <link rel="stylesheet" ng-href="style.css"/>

6 </head>

7 <body ng-controller="MainController">

8 <stacked-bar bars="bars"></stacked-bar>

9

10 <input type="number" ng-model="bars[0]" />

11 <input type="number" ng-model="bars[1]" />

12 <input type="number" ng-model="bars[2]" />

13 </body>

14 </html>

1 angular.module('cookbookApp', []) 2 .directive('stackedBar', function() {

3 return {

4 restrict: 'E',

5 template: '<div class="stacked-bar-chart">' + 6 '<div ng-repeat="bar in bars track by $index" ' + 7 'class="stacked-bar stacked-bar-{{$index}}" ' + 8 'ng-style="{width: bar+\'%\'}">' + 9 '</div>' + 10 '</div>', 11 scope : { 12 bars: '=' 13 } 14 }; 15 })

16 .controller('MainController', function($scope) { 17 $scope.bars = [30,30,40];

18 });

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-stacked-bar-chart

(57)

22 Prevent event propagation from

ng-click

Problem

You have several nested elements withng-click. If you click the front element, you don’t want the click function of the back element to be triggered.

Solution

The trick here is to use $event which is injected into ng-click.$event is the normal JavaScript event object and thus you can usestopPropagation()to prevent the bubbling of events.

You pass $eventas parameter of the topmostng-clickand callevent.stopPropagation()in the function declaration inside of the controller.

1 <div ng-click="back()">

2 ...

3 <a ng-click="front($event)">Click me!</a>

4 ...

5 </div>

Alternative solution

You can of course use$eventdirectly inside theng-clickdefinition. 1 <div ng-click="back()">

2 ...

3 <a ng-click="front(); $event.stopPropagation()">Click me!</a>

4 ...

(58)

23 Submit form on enter key

Problem

Usually, if you press enter in a form field, the form is submitted to the action defined in the ´<form> ´ tag but you want to call a function in your controller.

Solution

The solution is to leave out the actionattribute in <form>and useng-submit. The expression in ng-submitis what gets executed when you press return inside a form field.

An complete example:

1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 <link rel="stylesheet" ng-href="style.css"/>

6 </head>

7 <body ng-controller="MainController">

8 <form name="form" ng-submit="addTask(taskName)" novalidate>

9 <input type="text" ng-model="taskName" />

10 </form>

11

12 <ul>

13 <li ng-repeat="task in tasks track by $index" ng-bind="task"></li>

14 </ul>

15 </body>

(59)

Submit form on enter key 51 1 angular.module('cookbookApp', [])

2 .controller('MainController', function($scope) { 3 $scope.tasks = [];

4

5 $scope.addTask = function(taskName) { 6 $scope.tasks.push(taskName);

7 };

8 });

We usetrack by $indexinng-repeat here. This is a hack to allow duplicate entries in your list. Try to see what happens if you leave it out.

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-submit-form-on-enter

(60)

24 Make a syntax highlighter

Problem

You want to make a syntax highlighter but your code inside the code block is interpreted by AngularJS.

Solution

As an example, we take the following code: 1 <html ng-app="cookbookApp">

2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="prism/prism.js"></script>

5 <script src="application.js"></script>

6 <link rel="stylesheet" ng-href="prism/prism.css"/>

7 </head>

8 <body>

9 <pre><code class="language-markup">

10 <ul>

11 <li ng-repeat="task in tasks">{{ task }}</li>

12 </ul>

13 <button ng-click="add()">Add task</button>

14 </code></pre>

15 </body>

16 </html>

If we look at the code block, there are several problems we have to tackle.

• Start: From a html perspective, the code snippet starts right behindcode class="language-markup"> but we don’t want to insert an empty line before the code snippet.

• Indention: For reasons of readability, we want the indention in the html file to be 4 spaces. But when we present it to the user, we want it to start at 0.

• Angle brackets: We don’t want to paste our code like ‘>pre<. We have to convert it.

• AngularJS: We don’t want AngularJS to interpret the code inside the code block because if we would use an expression, it would be interpreted by AngularJS.

(61)

Make a syntax highlighter 53 As a syntax highlighter, we usePrism¹by Lea Verou. It’s used by a lot of well-known companies for their sites.

1 angular.module('cookbookApp', []) 2 .directive('code', function() {

3 function escapeAngleBrackets(text) {

4 return text.replace(/</gi, '&lt;').replace(/>/gi, '&gt;');

5 }

6 function trimSurroundingEmptyLines(text) {

7 return text.replace(/^(\n)*/, '').replace(/(\n)*(\s)*$/, '');

8 }

9 function fixIndention(text) {

10 return text.replace(

11 new RegExp('^ {' + text.search(/[^\s-]/) + '}', 'gm'), '');

12 }

13 return {

14 restrict: 'E', 15 terminal: true,

16 link: function(scope, element) {

17 var content = element.html();

18 content = escapeAngleBrackets(content); 19 content = trimSurroundingEmptyLines(content); 20 content = fixIndention(content); 21 element.html(content); 22 Prism.highlightElement(element.get(0)); 23 } 24 }; 25 });

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-syntax-highlighter

Online demo:http://sbrink.github.io/angularjs-cookbook-code/directives-syntax-highlighter/

(62)

25 Textarea char limit with remaining

counter

Problem

You want a counter for remaining chars for your textarea

Solution

We create a directive which appends an counter element after the textarea. To get the value of ng-model, we require thengModelcontroller which is the preferred way if there’s ang-model. For the counter of the remaining characters we create a template in a variablecounterTemplate. for this template we create a new fresh scopecounterScopeand compile the template against this scope. We then append it after the textarea element withelement.after(...).

Everytime there’s a change in the model, the parser chain of thengModelcontroller is called which we use. There we just calculate the current length, truncate it when it’s to long and refresh the variableremainingon thecounterScope.

1 angular.module('cookbookApp', [])

2 .directive('maxlengthCounter', function ($compile) {

3 var counterTemplate = '<p>Remaining charaters: {{remaining}}</p>';

4 return {

5 require:'ngModel',

6 link:function (scope, element, attrs, ngModelCtrl) {

7 var maxLength = parseInt(attrs.maxlengthCounter, 10); 8

9 var counterScope = scope.$new(); 10 counterScope.remaining = maxLength;

11 element.after($compile(counterTemplate)(counterScope)); 12

13 ngModelCtrl.$parsers.push(function (value) { 14 var currentLength = parseInt(value.length, 10); 15

16 if (currentLength > maxLength) {

(63)

Textarea char limit with remaining counter 55

18 currentLength = maxLength;

19 }

20 counterScope.remaining = maxLength - currentLength;

21 }); 22 } 23 }; 24 }); 1 <html ng-app="cookbookApp"> 2 <head>

3 <script src="../vendor/angular.js"></script>

4 <script src="application.js"></script>

5 </head>

6 <body>

7 <textarea ng-model="result" maxlength-counter="16"></textarea>

8 <p>{{result}}</p>

9 </body>

10 </html>

Code

Complete source: https://github.com/sbrink/angularjs-cookbook-code/tree/gh-pages/directives-textarea-maxlength-counter

(64)

26 Theme support

Problem

You want to support different themes which you can change on the fly.

Solution

Overview:

• Create a controller for the<head>tag which changes the href to your stylesheet on the fly. • Create a service which contains the active theme and supports the changing of it.

• Inject the service where you want to change the theme. The code:

First we create a ThemeService service , a HeadController controller and a MainController controller.

• ThemeServiceholds the theme state

• HeadControllerexposes getTheme() to change the stylesheet link in the<head>tag • MainControlleracts as an example for a controller which can change the theme.

1 angular.module('cookbookApp', []) 2 .factory('ThemeService', function(){

3 var themes = { available: ['default', 'greenish'], active: 'default' }; 4

5 function getTheme() { return themes.active; }

6 7 function setTheme(name) { 8 if (themes.available.indexOf(name) === -1) { return; } 9 themes.active = name; 10 } 11 12 return { 13 getTheme: getTheme,

References

Related documents

The particular conditions Arendt identifies are: the earth, which is the habitat in which we, along with all organic life, are able to “move and breathe without effort and

Did you have 2 or more hypomanic EPISODES ( C4b ) in your lifetime (including the current episode)? NO YES c) IF PAST “HYPOMANIC SYMPTOMS” IS CODED POSITIVE ASK:?. Did you have 2

Briefly, some of this research predicts that deregulation will lead to (i) more firms and less incumbent power (Blanchard and Giavazzi, 2003; Alesina et al., 2005); (ii) increases

To affirm that the greater N400 to subordinate homo-graphs judged sensible in the patients reflected an early stage semantic bias, it would be necessary to show a normal N400 to

In the end, CCGA recommended Council inform the Provost that effective immediately, CCGA has decided to invoke its delegated authority to require review of graduate programs

Though they operate outside of traditional broadcast models, connected TV platforms offer a viewing experience very similar to that of traditional linear television combined with

The purpose of this paper is to apply the PMG-based error correction model and the panel differenced GMM Arellano-Bond estimation to investigate effects of fiscal deficit and broad

All scaling factors A are lower than 1, so the amplitudes of all seismic phases are       overestimated by the PREM synthetic, presumably because the seismic moment reported in the