I’ve been playing around with Angular components at work lately, and attempting to mentally map my understanding of Angular components against the concepts found in React components, which I’ve had some experience with now.

I came across an interesting and peculiar design choice that Angular devs took for creating components in Angular. It was necessary to maintain the current directive functionality of how functions are linked and called.

The general convention when creating a component in TypeScript on Angular 1.5+ is this:

// main.ts

class MyComponent implements ng.IComponentOptions {

  public bindings: any;
  public controller: any;
  public controllerAs: string;
  public templateUrl: string = '';
  public replace: boolean = false;
  public transclude: any = false;

  constructor() {
    this.bindings = {
      close: '&'
    }

    this.controller = MyController;
    this.controllerAs = 'myCtrl';
  }
}

class MyController {

    public fieldIsTicked: boolean;
    public close: Function;

    constructor() { }

    $onInit() { }

    closeComponent() {
      if (this.close) {
        this.close()
      }
    }
}

angular.module('mymodule').component('myComponent', new MyComponent());
<!-- index.html -->

<my-component close="close($value)" />

In this code snippet, we’ve created a component with an associated controller. The component can be given a function, close() that maybe communicates to the parent element that it has completed its work.

But what if the higher level component wants to know some result of the lower level component, say fieldIsTicked? We’d want to call this.close(fieldIsTicked), for example, and include in the documentation for MyComponent that the first argument must be for that.

I did some experimenting and mucking around, and would find that the function would not receive the fieldIsTicked argument - it would always be undefined.

Digging through StackOverflow and the Angular docs, and later in the docs for the AngularUI modal, I found that people suggested instead to run this.close({$value: this.fieldIsTicked}).

I found that the convention is for developers of higher level components to supply the component not with a reference to the close() function itself, but providing an expression. When you call your this.close({$value: this.fieldIsTicked}), Angular unpacks the provided hash and applies the variables to the parent context, compiles the expression ‘$close($value)’ with the current context (i.e. the component’s parent) and runs the expression against the parent.

It begs the question: why does Angular do this, over simply calling Function.apply(args…)?

The benefit of this approach is that the child component (with its isolated scope) can still run functions against a parent’s $scope and access $scope variables. This design decision is a legacy artifact as a provision for developers who use $scope - which, had Angular 1.x been designed today, would not have been necessary. It makes sense in the context of directives, which were more tricky beasts and aimed to do multiple things at once, and the Angular 1.x developer’s reliance on $scope variables to get any work done.

Unfortunately, the biggest reason to use directives now (99% of use cases on the front end) is as components, and as people move to React/Angular 2 style definitions, it’s the norm that low level components won’t have access to higher level $scope variables unless they’ve been explicitly passed down.

As a result, the child component has to be aware of the parent’s argument names and linking methodology. Concerningly, this seems to break the Law of Demeter[1], whereas many other Angular design decisions were chosen intentionally to maintain this very law.

The need for this functionality to be maintained for backwards compatibility with directive ideals can be seen in the Angular docs on directives:

“Often it’s desirable to pass data from the isolate scope via an expression to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper function. For example, the hideDialog function takes a message to display when the dialog is hidden. This is specified in the directive by calling close({message: ‘closing for now’}). Then the local variable message will be available within the on-close expression.”

It’s clear that this was an important use case at some point in time. However, now we’ve got a huge number of Angular 1.x developers and a swathe of libraries and extensions which will have to be converted to Angular 2 at some stage. It’s not going to be a pretty effort back-porting with gotchas like this, buried deep in the Angular specs.

I personally believe the optimal decision to take would have been simplified components with a vastly more simple API, rather than having components as syntactic sugar for directives. There’s an opportunity here to grab developers who aren’t ready to plunge into the concepts of React, and in fact like the hand-holding DI service-based ways of AngularJS but want to move to a compositional “componentised” web dev workflow.

I believe there’s a middle ground here and Angular 2 could potentially nail it.


  1. The Law of Demeter is roughly that components of a software system should only talk to other components of a similar level, and subcomponents that are relied on by them should be composed inside them and not exposed to the other components. ↩︎