Angular 2.0 favors class-based designs with annotations as metadata.
- AtScript
- All about Angular 2.0
- Design doc for Angular 2.0
- Preparing for the future of Angular.js
- Angular Resources
- The problem with Angular
- What's wrong with Angular.js
- You have ruined JavaScript
- Change detection in Angular 2
- Building Angular apps using Flux architecture
Think of Angular as a Model View View Model.
- Getting up to speed with Angular.js in one day
- Model - The services (business logics and domain models)
- View - HTML template
- View Model - $scope + controller
- Angular Gap
- Angular vs Ember
- John Lindquist
- AngularJS is too humble to say you're doing it wrong
- Introduction to Angular in 50 examples
- AngularJS and D3
- Make your own AngularJS
- Angular for beginner
List of books
Features of Angular:
- Plain JavaScript. Not extending and not inheriting. Make testing easy.
- Data binding
- Routing/PushState
- Testing
- Templates/Directives/Controllers
- Modular
- Dependency injection
- jqLite? jQuery built in?
- jQuery plugins require custom directives (cons)
- Large apps requiring self-imposed structure (cons)
Executed when all modules have been loaded.
angular.module('MyApp', [])
.constant('VERSION', '1.0')
.run(['VERSION', '$rootScope', function(VERSION, $rootScope) {
}]);
Decorating function.
angular.module('myPlane.models')
.factory('Proposal', function(Model) {
return Model.extend({
memoize: ['revenue', 'cost'],
get profit() {
return this.revenue.minus(this.cost);
},
get revenue() {
return this.price.convertTo(this.internalCurrency);
}
});
});
Services and modules. Promises and directives.
angular.module('app', []);
Break module by feature. As Angular will add dynamic loading of module. So base on feature is better.
Services is where most of your code are.
- Factory
- Provider
- Services carry out common tasks specific to the web application
- Services are consumed via Dependency Injection
- Services are application singletons. One service per application.
- Services are instantiated lazily
Use $rootScope as a event bus to broadcast event. Avoid $rootScope for sharing data, use Services. Use custom crazy event systems at your own risk. Rely on binding!
$rootScope.$broadcast(EVENT_NAME, {data: 'Some data'});
$scope.$on(EVENT_NAME, fn);
Or use promises instead of $rootScope. Use interceptors for global-level events.
1 route to 1 controller to 1 view. Or use AngularUI Router to have master-detail case.
http://joelhooks.com/blog/2013/07/22/the-basics-of-using-ui-router-with-angularjs/
angular.module('MyApp', [])
.constant('VERSION', '1.0')
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
controller: 'HomeController',
templateUrl: './home.html'
});
}]);
Don't use ng-init if you can. Better to use true controller.
PipelineApp.controller('KanbanController', ['$scope', '$http', '$routeParams', function($scope, $http, $routeParams) {
}]);
Controller don't know about the view.
Controllers should not talk to each other. But in rare cases where you need to, what can you do?
Use a service to facilitate communication. $scope has events built in after the compilation cycle complete.
$broadcast- sends events downward from parent to children$emitsends events upwards$onlistens for events
One of the few acceptable time where you can use $rootScope as an event bus.
How do we connect template to controller. We do it through $scope. $scope will be the observable and notifier.
$scope is the glue between the Controller and the View.
$scope also provide context.
$rootScope is the topmost Scope object with all other children $scope
View = $compile(HTML, $scope)
When to use directives?
Domain Specific Languages.
- If you want a reusable HTML component like
<my-widget> - If you want reusable HTML behavior like
ng-click - If you want to wrap a jQuery plugin
<div ui-date> - Almost any time you need to interface with the DOM. Don't interact with the DOM in controller, service or anywhere except in directive.
angular.module('CpDirectives', []); // It's own directive module
angular.module('CpApp', ['CpDirectives']); // Load the directives in your app
// Code the directive
angular.module('CpDirectives')
.directive();
Don't use jQuery, but rather just use directive.
// jQuery
$('truck');
// Angular
<truck></truck>
Don't manipulate DOM in controller, do that in directives.
We have 3 directives here:
<body ng-app>
<ng-view></ng-view> <!-- like yield? -->
<ul>
<li ng-repeat="comment in comments">
{{comment.body}}
</li>
</ul>
</body>
App.directive("upCaser", function() {
return {
link: function(scope, el, attrs) {
$(el).find('p').each(function(i, p) {
p = $(p);
p.text(p.text().toUpperCase());
});
// Can watch also
scope.$watch('pressed', function() {
$(el).text('I am pressed!');
});
}
};
});
<div up-caser>
<p>some text</p>
</div>
Why write custom directives?
- Reusable component
- Manipulate the DOM
angular.module('ats.pipeline').directive('ats:stage', function() {
return {
template: '',
replace: true,
restrict: 'E',
scope: {
type: '@stageType', // interpolated
currentStage: '=active', // data bind
onNextStage: '&' // pass a function (callback)
},
link: function() {
// Do DOM manipulation here
}
};
});
The link function is where DOM manipulation happens. Linker can know about controller, but controller better don't know about linker for testability.
Isolated scope is important in directive. Prevent you to modify your parent scope's data. You probably do not need compile in directive.
The link function is where DOM manipulation happens.
- The
scopeis the same$scopein the controller function elementis a jQuery instance of the DOM element where the directive is declared onattrsis a list of attributes (normalized array) declared on the element
.directive('myCircle', function() {
return {
templateUrl: './circle.html',
link: function(scope, element) {
element.on('click', function() {});
}
};
});
The controller is constructed during the pre-linking phase. It receives $scope which is the current scope for the element.
The setup of the event is still best done at the link function, but any other logics can be inside this controller function.
.directive('myCircle', function() {
return {
templateUrl: './circle.html',
controller: ['$scope', '$rootScope', function($scope, $rootScope) {
$scope.afterClick = function() {
$rootScope.$broadcast('clicked');
};
}],
link: function(scope, element) {
element.on('click', function() {
scope.$apply(function() {
scope.afterClick(); // ask the controller for help
});
});
}
};
});
// Directive call one time, and link call multiple times depending on how many inputs you have.
<input type="text" form-select-all-on-focus />
angular.module('CpDirectives')
.directive('formSelectAllOnFocus', function() {
return {
restrict: 'A',
link: function(scope, element) {
element.mouseup(function(event) {
event.preventDefault();
});
element.focus(function() {
element.select();
});
}
};
});
- ngIf
- ngShow / ngHide
- ngRepeat
- ngSwitch
- ngInclude
Directives that call methods and provide data to controller.
- ngModel - attach properties to
$scope - ngBlur / ngFocus
- ngClick
- ngSubmit
- ngClass
ng-class="{'label-success':focus}"
Like Unix's pipe. Output of one expression and pipe it into another.
<li ng-repeat='c in comments | orderBy: "-date"'>
{{c.author | uppercase}}
</li>
window.App = angular.module('app', []);
App.filter("salaryRange", function() {
return function(input) {
return input.min + " - " + input.max
};
});
{{'1388123412323' | date:'MM/dd/yyyy @ H:mma'}}
{{'gem' | uppercase}}
{{'My Description' | limitTo:8}}
- html2js
- transpilers
- Ionic
- ng-upload
- Restangular
- angular-ui
- angular-ui-utils
- angular-bootstrap (learn the testing of directives?)
<html ng-app="myApp">
<script>
// The active profile is populated using server side rendering
var myActiveProfile = { "id": 123, "name": "Ido", "language": "en" };
</script>
</html>
// Later
angular.module('myApp', []).config(['$provide', function($provide) {
var profile = angular.copy(window.myActiveProfile);
profile.canEditAccount = function() {
return profile.isAccountManager || profile.permissions.editAccount;
};
// Provide the active profile as an injectible constant.
$provide.constant('activeProfile', profile);
}]);
Testing filter
describe('filter', function() {
beforeEach(module('phonecatFilter'));
describe('checkmark', function() {
it('should convert boolean value to unicode checkmark or cross', inject(function(checkmarkFilter) {
expect(checkmarkFilter(true)).toBe('\u2713');
expect(checkmarkFilter(false)).toBe('\u2718');
}));
});
});