Over a million developers have joined DZone.

Updated AngularDart for AngularJS Developers.

· Java Zone

Microservices! They are everywhere, or at least, the term is. When should you use a microservice architecture? What factors should be considered when making that decision? Do the benefits outweigh the costs? Why is everyone so excited about them, anyway?  Brought to you in partnership with IBM.

AngularDart is a port of the acclaimed framework to the Dart platform. It is being developed by the Angular core team. In this article I will compare the Dart and JS versions of the framework. In particular, I will look into dependency injection, directives, and digesting.

UPDATED VERSION

The original version of this article was written a few months ago. Because the AngularDart framework is still in the works, quite a few things have changed since then. This updated version covers the changes.

INTENDED AUDIENCE

The article is written for:

  • Dart developers who have some experience with AngularJS.
  • AngularJS developers thinking about trying out AngularDart.
  • AngularJS developers who are not going to switch to Dart, but want to know more about the future of the framework. According to Angular folks, many of AngularDart’s features will be ported to AngularJS at some point. So learning about the Dart version of the framework can be interesting, even if you are not planning to use it.

DEPENDENCY INJECTION

INJECTION BY NAME VS. INJECTION BY TYPE

AngularDart makes interesting use of the Dart optional type system: it uses type information to configure the injector. In other words, the injection is done by type, not by name.

//JS:
// The name here matters, and since it will be minified in production, 
// we have to use the array syntax.
m.factory("usersRepository", ["$http", function($http){
return {
all: function(){/* ... */}
}
}]);
//DART:
@Injectable()
class UsersRepository {
// Only the type here matters, the variable name does not affect DI.
UsersRepository(Http h){/*...*/}
all(){/* ... */}
}

REGISTERING INJECTABLE OBJECTS

In AngularJS an injectable object can be registered with the Angular DI system using filter, directive, controller, value,constant, service, factory, or provider.

Methods Purpose
filter Registering filters
directive Registering directives
controller Registering controllers
value, constant Registering configuration objects
service, factory, provider Registering services

As you can see, there are a lot of ways to register an injectable object, which often confuses developers. Partially, it is due to the fact that filter, directive, and controller are all used for different types of objects, and thus not interchangeable. Theservice, factory, and provider functions, on the other hand, are all used to register services, with provider being the most generic one.

AngularDart takes a very different approach: it separates the type of an object from how it is registered with the DI system. In AngularDart any object can be registered using bind.

Methods Purpose
bind Registering all objects

It can be done as follows.

//DART:




// The passed in object will be available for injection.
bind(UsersRepositoryConfig, toValue: new UsersRepositoryConfig());




// AngularDart will resolve all the dependencies 
// and instantiate UsersRepository. 
bind(UsersRepository);




// AngularDart will call the factory function. 
// You will have to resolve the dependencies using the passed in injector 
// and then instantiate UsersRepository.
bind(UsersRepository, toFactory: (Injector inj) => new UsersRepository(inj.get(Http)));

The fact that this one function can be used to register any object is a substantial simplification of the API.

Any class can be used as a service. You just need to register it with the Angular DI system. When the time comes, Angular will instantiate an instance of that class, and inject all the dependencies through constructor arguments.

Other types of objects, however, have to provide some extra information, which you use annotations for.

//DART:




@Component(
selector: 'users',
templateUrl: 'lib:components/users.html'
)
class UsersComponent {
UsersComponent(UsersRepository repo){
//...
}
}

In AngularDart the type of an injectable object and how it is registered with the DI system are two orthogonal concerns.

CREATING MODULES AND BOOTSTRAPPING AN APPLICATION

The following is the standard way of creating an application in AngularJS.

//JS:
var m = angular.module("users", ['common.errors']);
m.service("usersRepository", UsersRepository);
angular.bootstrap(document, ["users"]);

Which maps pretty closely to AngularDart.

//DART:
final users = new Module()
..bind(UsersRepository)
..install(new CommonErrors());




applicationFactory().addModule(users).run();

Another way to do that is by extending Module.

//DART:
class Users extends Module {
Users(){
bind(UsersRepository);
install(new CommonErrors());
}
}




applicationFactory().addModule(new Users()).run();

This way is preferable when you want to wire up your components differently based on, for instance, the platform the application is running on.

CONFIGURING INJECTABLE OBJECTS

AngularJS provides multiple options for configuring injectable objects. The simplest one is to inject a configuration object using value.

//JS:
m.value("usersRepositoryConfig", {login: 'jim', password: 'password'});
m.service("usersRepository", function (usersRepositoryConfig){ 
//...
});

Same can be done in Dart.

//DART:
class UsersRepositoryConfig {
String login;
String password;
}




class UsersRepository {
UsersRepository(UsersRepositoryConfig config){/* ... */}
}




bind(UsersRepository);
bind(UsersRepositoryConfig, toValue: new UsersRepositoryConfig()..login="Jim"..password="password");

Now, suppose UsersRepository takes two arguments instead of a hash and we cannot change that. In this case, we would usefactory.

//JS:
m.value("usersRepositoryConfig", {login: 'jim', password: 'password'});
m.factory("usersRepository", function (usersRepositoryConfig){ 
return new UsersRepository(usersRepositoryConfig.login, usersRepositoryConfig.password);
});

The AngularDart version, once again, is very similar.

//DART:
bind(UsersRepositoryConfig, toValue: new UsersRepositoryConfig()..login="Jim"..password="password");




bind(UsersRepository, toFactory: (Injector inj){
final c = inj.get(UsersRepositoryConfig);
return new UsersRepository(c.login, c.password);
});

Some prefer defining a provider for this purpose.

//JS:
m.provider("usersRepository", function(){
var configuration;




return {
setConfiguration: function(config){
configuration = config;
},




$get: function($modal){
return function(){
return new UsersRepository(configuration);
}
}
};
});

The setConfiguration method has to be called during the configuration phase of the application.

//JS:
m.config(
function(usersRepositoryProvider){
usersRepositoryProvider.setConfiguration({login: 'Jim', password: 'password'});
}
);

Since AngularDart has neither providers nor an explicit configuration phase, the example could not be directly translated into Dart. This is the closest I came up with.

//DART:
final users = new Module()..bind(UsersRepositoryConfig)
..bind(UsersRepository);




Injector inj = applicationFactory().addModule(users).run();




inj.get(UsersRepositoryConfig)..login = "jim"
..password = "password";

DIRECTIVES, CONTROLLERS VS. DECORATORS, COMPONENTS, CONTROLLERS

Now, let’s switch gears and talk about another pillar of the framework - directives.

Though AngularJS directives are extremely powerful and, in general, easy to use, defining a new directive can be confusing. I think the Angular team realized that, and that is why the API of the Dart version of the framework is very different.

In AngularJS there are two types of objects used to organize UI interactions:

  • Directives encapsulate all interactions with the DOM. They are declarative, and can be viewed as a way to extend html.
  • Controllers are imperative. They are unaware of the DOM, and can contain application logic.

In AngularJS these two types of objects are distinct: different helpers are used to register them, and completely different APIs are used to define them.

The AngularDart world is very different. In AngularDart there are Decorators, Components, and Controllers, and all of them are directives.

AngularJS AngularDart
Directives without a template Decorators
Directives with a template Components
Controllers Controllers

In AngularDart, decorators are mostly used for augmenting DOM elements. When you want to define a new custom element, you use components. You use controllers to implement application logic.

Let’s look at a few examples.

DIRECTIVES/DECORATORS

The vs-match directive can be applied to an input element. It listens to the changes on that element, and when the value matches the provided pattern, the directive will add the match class to the element.

It can be used as follows:

<input type="text" vs-match="^\d\d$">

This is a very simple AngularJS implementation of the described directive:

//JS:
directive("vsMatch", function(){
return {
restrict: 'A',




scope: {pattern: '@vsMatch'},




link: function(scope, element){
var exp = new RegExp(scope.pattern);
element.on("keyup", function(){
exp.test(element.val()) ? 
element.addClass('match') : 
element.removeClass('match');
});
}
};
});

Now, let’s compare it with the AngularDart version.

//DART:
@Decorator(selector: '[vs-match]')
class Match implements AttachAware{
@NgAttr("vs-match")
String pattern;




Element el;




Match(this.el);




attach(){
final exp = new RegExp(pattern);
el.onKeyUp.listen((_) =>
exp.hasMatch(el.value) ?
el.classes.add("match") :
el.classes.remove("match"));
}
}

Let me walk you through it:

  • Decorator is meta-data marker that tells Angular that this class is a decorator.
  • The selector property defines when this decorator is activated. In this case, it is when an element has the vs-matchattribute.
  • In addition to being able to inject any service into a decorator, you can also inject the element the decorator is applied to. That is what Match(this.el) is doing. In Angular 1.X everything that is registered with the DI must be a singleton. This obviously would not work here, because different instances of the Match directive would need different elements. To make this kind of injection possible AngularDart implements hierarchical injectors.
  • Bindings can be set up by passing a map, similar to AngularJS. But you can also do that by using annotations, which I did in the example.
  • When the constructor of the decorator is invoked, the pattern value has not been bound yet. The solution is to implementAttachAware. It provides the attach method, which will be invoked when the next digest occurs. At that point all the attribute mappings are processed, so I can safely construct the regular expression.
  • Finally, there are no link or compile functions.

COMPONENTS

The component we are going to look into next toggles the visibility of its content, and it can be used as follows:

<toggle button="Toggle">
<p>Inside</p>
</toggle>

This is an AngularJS implementation of this component:

//JS:
directive("toggle", function(){
return {
restrict: 'E',




replace: true,
transclude: true,




scope: {button: '@'},




template: "<div><button ng-click='toggle()'>{{button}}</button><div ng-transclude ng-if='showContent'/></div>",




controller: function($scope){
$scope.showContent = false;
$scope.toggle = function(){
$scope.showContent = !$scope.showContent ;
};
}
}
})

Now, let’s contrast it with the Dart version:

//DART:
@Component(
selector: "toggle",
publishAs: 't',
template: "<button ng-click='t.toggle()'>{{t.button}}</button><content ng-if='t.showContent'/>"
)
class Toggle {
@NgAttr("button")
String button;




bool showContent = false;
toggle() => showContent = !showContent;
}
  • Component tells Angular that this class is a component.
  • publishAs is the name we can use in the template to access the toggle object. It is worth noting that t is available only in the template of this component, not in the inserted content.
  • template, not surprisingly, defines how this custom element is rendered.

Though the JS and Dart versions look similar, under the hood there are important differences.

An AngularDart component uses shadow DOM to render its template.

AngularJS:

AngularJS Component

AngularDart:

AngularDart Component

Shadow DOM gives us the DOM and CSS encapsulation, which is great for building reusable components. Also, the API has been changed to match the web components specifications (e.g.,ng-transclude was replaced with content).

If for some reason you do not want to use shadow DOM, it can be disabled as follows:

@Component(
selector: "toggle",
publishAs: 't',
template: "<button ng-click='t.toggle()'>{{t.button}}</button><content ng-if='t.showContent'/>",
useShadowDom: false
)
class Toggle {
//...
}

An AngularDart component uses a template element to store its template.

This removes the need of hacks such as ng-src.

To recap, decorators are used to augment DOM element. Components are a lightweight version of web-components, and they are used to create custom elements.

CONTROLLERS

The following example shows a very simple controller implemented in AngularJS.

//JS:
<div ng-controller="CompareCtrl as ctrl">
First <input type="text" ng-model="ctrl.firstValue">
Second <input type="text" ng-model="ctrl.secondValue">




{{ctrl.valuesAreEqual()}}
</div>
controller("CompareCtrl", function(){
this.firstValue = "";
this.secondValue = "";




this.valuesAreEqual = function(){
return this.firstValue == this.secondValue;
};
});

The Dart version is quite different.

//DART:
<div compare-ctrl>
First <input type="text" ng-model="ctrl.firstValue">
Second <input type="text" ng-model="ctrl.secondValue">




{{ctrl.valuesAreEqual}}
</div>




@Controller(
selector: "[compare-ctrl]",
publishAs: 'ctrl'
)
class CompareCtrl {
String firstValue = "";
String secondValue = "";




get valuesAreEqual => firstValue == secondValue;
}

Controllers are basically decorators that create a new scope at the element. All the options that can be used when defining a new decorator can also be used when defining a controller. Having said that, it is still a good idea to avoid putting any DOM manipulation logic into your controllers, even though it is not enforced by the framework.

FILTERS/FORMATTERS

Finally, let’s look at how you can define a filter.

//JS:
filter("isBlank", function(){
return function(value){
return value.length == 0;
};
});

and the Dart version:

//DART:
@Formatter(name: 'isBlank')
class IsBlank {
call(value) => value.isEmpty;
}

There is not much different here, apart from the fact, that filters have been renamed into formatters.

ZONES & $SCOPE.$APPLY

Experienced Angular developers will appreciate the next feature. One may argue it has the most impact from the ones I covered in this article:

There is no need to call $scope.$apply when integrating with third-party components.

Let me illustrate it with the following example.

<div ng-controller="CountCtrl as ctrl">
{{ctrl.count}}
</div>

CountCtrl is a controller that just increments the count variable.

//JS:
controller("CountCtrl", function(){
var c = this;
this.count = 1;




setInterval(function(){
c.count ++;
}, 1000);
})

An experienced AngularJS developer will notice right away that the code is broken. Angular just cannot see that the countvariable has been changed in the callback. To fix this issue you have to wrap it in $scope.$apply, as follows:

//JS:
controller("CountCtrl", function($scope){
var c = this;
this.count = 1;




setInterval(function(){
$scope.$apply(function(){
c.count ++;
});
}, 1000);
})

This is a fundamental limitation of AngularJS - you need to tell Angular to check for changes. The frameworks tries to minimize the number of places where you have to do that by having a futures library bundled, and providing the $interval service. But the moment you start using some other futures library or, in general, integrating with async third-party components, you will have to use $scope.$apply.

Now, let’s contrast it with the Dart version.

//DART:
<div count-ctrl>
{{ctrl.count}}
</div>




@Controller(
selector: "[count-ctrl]",
publishAs: 'ctrl'
)
class CountCtrl {
num count = 0;




CountCtrl(){
new Timer.periodic(new Duration(seconds: 1), (_) => count++);
}
}

The Dart version works even though there is no $apply, and Timer knows nothing about Angular. That is fantastic! To understand how this works, we need to learn about the concept of Zones.

Dart docs: A Zone represents the asynchronous version of a dynamic extent. Asynchronous callbacks are executed in the zone they have been queued in. For example, the callback of a future.then is executed in the same zone as the one where the then was invoked.

You can think of a Zone as a thread-local variable in an event-based environment. The environment always has the current zone, and all the callbacks of all async operations go through the current zone. That gives Angular a place to check for changes.

In addition, using this mechanism the framework can collect information about the execution of your program, and, for example, generate long stack traces. So when an exception is thrown, you will see the stacktrace crossing multiple VM turns. Needless to say, it dramatically improves the dev experience.

WRAPPING UP

  • AngularDart APIs are class-based.
  • The framework uses injection by type instead of injection by name.
  • The types of objects are decoupled from how they are registered with the DI system.
  • Annotations such as Decorator and Component are used to configure injectable objects.
  • Decorators, components, controllers, formatters, and services all can be registered using bind.
  • Decorators are used to augment DOM elements.
  • Components are a lightweight version of web-components, and they are used to create custom elements.
  • By default, components use shadow DOM to render their templates.
  • Controllers are decorators that create a new scope at the element they are applied to.
  • The scope is digested automatically through Dart zones, eliminating the need for scope.$apply.

WHAT IS GOING TO BE PORTED TO JS

Based on the talk Misko and Igor gave at Devoxx, it looks like most of the changes are going to be ported to AngularJS, in particular:

  • Type-based injection
  • Hierarchical injectors
  • Using annotations for defining objects
  • Using shadow DOM
  • Zones

LEARN MORE

I hope this article gives you enough information to get started. If you want to learn more check out:

COMING CHANGES

There are a few major changes coming to AngularDart:

  • The new bind- syntax instead of @NgAttr.
  • Controllers are deprecated and will be removed.

The article will be updated once these changes make into the framework.

TRANSLATIONS

Yasushi Ando translated the original version of this article to Japanese. You can find the translation here.

Discover how the Watson team is further developing SDKs in Java, Node.js, Python, iOS, and Android to access these services and make programming easy. Brought to you in partnership with IBM.

Topics:

Published at DZone with permission of Victor Savkin, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}