For the last few years, I have been working with Angular frame work for different products. Here are some solutions that benefit me in my maintaining progress Angular 1 code.
By default, all the functions in directive
are hooking with $scope
. As long as the functions are inside the directive
scope in HTML, the app would be functional.
// sample.js
var sample = () => {
return {
controller: sampleCtrl,
restrict : 'A',
scope: false
};
};
sampleCtrl.$inject = ['$scope'];
function sampleCtrl($scope) {
$scope.acc = 0;
$scope.add = () => {
$scope.acc++;
};
}
// app.js
var app = angular.module('app', []);
app.directive('sample', sample);
<!DOCTYPE html>
<html>
<body>
<div ng-app="app">
<div sample>
<button ng-click="add()">click here</button>
<h1>here goes {{acc}}</h1>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</body>
</html>
This is reasonable solution when the we only have to delivery simple and straightforward components. The problems would rise when we add a child component
// sample.js
var sample = () => {
return {
controller: sampleCtrl,
restrict : 'A',
scope: false
};
};
sampleCtrl.$inject = ['$scope'];
function sampleCtrl($scope) {
$scope.acc = 0;
$scope.add = () => {
$scope.acc++;
};
}
// word.js
var word = () => {
return {
controller: wordCtrl,
restrict : 'A',
scope: true
};
};
function wordCtrl($scope) {
$scope.text = 'here';
$scope.update = () => {
$scope.text = $scope.text === 'here' ? 'there' : 'here';
};
}
// app.js
var app = angular.module('app', []);
app.directive('sample', sample)
.directive('word', word);
<!DOCTYPE html>
<html>
<body>
<div ng-app="app">
<div sample>
<button ng-click="add()">+1</button>
<div word>
<button ng-click="update()">switch</button>
<h1>{{text}} goes {{acc}}</h1>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script src="index.js"></script>
</body>
</html>
Now it is harder to find where are add()
and update()
functions located, since the boundary of sample
and word
are not clear.
To make this more readable and maintainable, we could introduce controllerAs
// sample.js
var sample = () => {
return {
controller: sampleCtrl,
controllerAs: 'sampleCtrl',
restrict : 'A',
scope: false
};
};
sampleCtrl.$inject = ['$scope'];
function sampleCtrl($scope) {
let ctrl = angular.extend(this, {});
$scope.acc = 0;
ctrl.add = () => {
$scope.acc++;
};
}
// word.js
var word = () => {
return {
controller: wordCtrl,
controllerAs: 'wordCtrl',
restrict : 'A',
scope: true
};
};
function wordCtrl($scope) {
let ctrl = angular.extend(this, {});
$scope.text = 'here';
ctrl.update = () => {
$scope.text = $scope.text === 'here' ? 'there' : 'here';
};
}
//app.js
var app = angular.module('app', []);
app.directive('sample', sample)
.directive('word', word);
<!DOCTYPE html>
<html>
<body>
<div ng-app="app">
<div sample>
<button ng-click="sampleCtrl.add()">+1</button>
<div word>
<button ng-click="wordCtrl.update()">switch</button>
<h1>{{text}} goes {{acc}}</h1>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script src="index.js"></script>
</body>
</html>
With the this update, it is easier to find the function you would like to update with the controller name.
Now it is the time to improve readablity of render section => {{text}}
and {{acc}}
. by using Ng inspector, we can check data with live page.
It is the equivalent of 'React dev tool' to React. Talking about React, I found the this.state
in react is a much better way of logging angular changes, so let's implement that to angular 1.
// sample.js
var sample = () => {
return {
controller: sampleCtrl,
controllerAs: 'sampleCtrl',
restrict : 'A',
scope: false
};
};
function sampleCtrl() {
let ctrl = angular.extend(this, {});
ctrl.state = {
acc: 0
};
ctrl.add = () => {
ctrl.state.acc++;
};
}
// word.js
var word = () => {
return {
controller: wordCtrl,
controllerAs: 'wordCtrl',
restrict : 'A',
scope: true
};
};
function wordCtrl() {
let ctrl = angular.extend(this, {});
ctrl.state = {
text: 'here'
};
ctrl.update = () => {
ctrl.state.text = ctrl.state.text === 'here' ? 'there' : 'here';
};
}
//app.js
var app = angular.module('app', []);
app.directive('sample', sample)
.directive('word', word);
<!DOCTYPE html>
<html>
<body>
<div ng-app="app">
<div sample>
<button ng-click="sampleCtrl.add()">+1</button>
<div word>
<button ng-click="wordCtrl.update()">switch</button>
<h1>{{wordCtrl.state.text}} goes {{sampleCtrl.state.acc}}</h1>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script src="index.js"></script>
</body>
</html>
With ng-inspector and state, our code is now well self-document guide for maintaining directives in angular 1
Things could be more complicated than this; let's say instead of here goes
we shall say here comes
. It is could be a good time for us to sort out directive functions with presentational function and container
- Are concerned with how things look.
- Have no dependencies on the rest of the app
- Don’t specify how the data is loaded or mutated.
- Are pure functions
- Are concerned with how things work.
- Provide the data and behavior to presentational or other container components.
- Are often stateful, as they tend to serve as data sources.
// word.js
var word = () => {
return {
controller: wordCtrl,
controllerAs: 'wordCtrl',
restrict : 'A',
scope: true
};
};
function wordCtrl() {
let ctrl = angular.extend(this, {});
// Presentational
ctrl.wording = (acc, text = ctrl.state.text) => {
return text === 'here' ? 'here comes ' + acc : 'there goes ' + acc;
};
// Container
ctrl.state = {
text: 'here'
};
ctrl.update = () => {
ctrl.state.text = ctrl.state.text === 'here' ? 'there' : 'here';
};
}
<!DOCTYPE html>
<html>
<body>
<div ng-app="app">
<div sample>
<button ng-click="sampleCtrl.add()">+1</button>
<div word>
<button ng-click="wordCtrl.update()">switch</button>
<h1>{{wordCtrl.wording(sampleCtrl.state.acc)}}</h1>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script src="index.js"></script>
</body>
</html>
Now, our presentational functions are pure functions, so when we have a error in render and state
data is correct, then it must be errors in presentational functions.
On the other hand, when state
data is wrong, we shall be able to find errors in containers
It is noticeable that, we could use publish/subscribe
or middleware like Redux to passing data between word
and sample
in a complex real app.
With controllerAs
, state
logging and "presentational function and container" sorting , we have a code base with more confidence in maintaining angular one