Skip to content

Instantly share code, notes, and snippets.

@seeliang
Last active January 27, 2019 22:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seeliang/937ab8df86233ac715e4c5bc331d56a2 to your computer and use it in GitHub Desktop.
Save seeliang/937ab8df86233ac715e4c5bc331d56a2 to your computer and use it in GitHub Desktop.
Write more readable code in angular 1 with state, controllerAs, and presentational and container structure

Write more readable code in angular 1

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.

Use controllerAs instead of $scope for directives

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.

Logging state like react

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

Implement Presentational and Container structure

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

Presentational functions

  • 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

Containers

  • 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

Note

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.

Conclusion

With controllerAs, state logging and "presentational function and container" sorting , we have a code base with more confidence in maintaining angular one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment