scalajs-angular aims to help developers build AngularJS based applications in type safe manner with Scala language.
To achieve this goal, it depends on Scala.js to provide bindings to core AngularJS classes and functions, as well as its own APIs to enable Scala developers to access them in more natural manner.
It's still at the very early stage of development, so the most parts of the project are subject to frequent and extensive changes.
And the bindings are by no means comprehensive or exhaustive for now, so please use it at your discretion.
Add the following line to your sbt
build definition:
libraryDependencies += "com.greencatsoft" %%% "scalajs-angular" % "0.2"
If you want to test the latest snapshot version instead, change the version to "0.3-SNAPSHOT" and add Sonatype Snapshot Repository to the resolver as follows:
resolvers +=
"Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
You can define an AngularJS module in the following manner. Note that config
and
controller
methods can be chained and take variable arguments.
val module = Angular.module("myproject", Seq("ngRoute", "ui.bootstrap"))
module.config(RoutingConfig)
module.factory(UserServiceFactory)
module.controller(UserDetailController)
module.directive(UserInfoDirective)
module.run(AppInitializer)
Note: Although the API itself supports method chaining, it might not work correctly in this version due to a limitation in current macro implementation.
You can find core AngularJS services like HttpService
or Location
in the
core
package, while those from any third party modules reside in the extensions
package.
And such dependencies can be injected into any object which inherits from the Service
trait, including Controller
, Directive
, Factory
, and more.
To inject a specific dependency, you can declare a variable with the @inject
annotation like the following example:
object ExampleController extends Controller {
@inject
var location: Location = _
@inject
var http: HttpService = _
// You can assume all dependencies to be resolved
// after this method is invoked.
override def initialize() {
super.initialize()
val url = location.absUrl + "/example"
http.get(url).success(success).error(failure)
}
}
You can also declare your own service, by annotating it with the @injectable
annotation, and register it using a factory:
@injectable("$taskService")
class TaskService(val http: HttpService) {
// implement service methods.
}
object TaskServiceFactory extends Factory[TaskService] {
override val name = "$taskService"
@inject
var http: HttpService = _
override def apply(): TaskService = new TaskService(http)
}
object TaskController extends Controller {
@inject
var service: TaskService = _
override def initialize() {
// Use the injected service.
}
}
For the sake of example, let's assume that you have a REST API url "/users/john"
that produces a JSON output that could be unpickled to the following User
case class :
@JSExportAll
case class User(id : String, name : String, email : String, friends : Array[String])
To see an example of how such unpickling can be done, see the TaskService from the TodoMVC sample
Normally, you'll need to retrieve some data from the server with the http
service, and
make it available to the HTML template by assigning it to a property of the $scope
.
object UserDetailsController extends Controller with HttpServiceAware {
// If not overriden, it'll use the simple class name.
override val name = "UserDetailsCtrl"
override type ScopeType = UserForm
override def initialize(scope: ScopeType) {
val future: Future[User] = http.get("/users/john")
future onComplete {
case Success(user) => {
scope.id = user.id
scope.name = user.name
scope.email = user.email
scope.friends = user.friends
}
case Failure(t) => println("An error has occured: " + t.getMessage)
}
scope.dynamic.delete = () => userService.delete(scope.id)
}
trait UserForm extends Scope {
var id: String
var name: String
var email: String
var friends: js.Array[String]
}
}
ScopeAware
(which is inherited from the Controller
trait) defines an abstract
type member ScopeType
with which you can access the scope object in a type safe
manner. Due to a restriction in Scala.js, the target class should inherit from the
js.Object
and you cannot declare any methods in it.
To workaround the problem, you need to cast the scope
variable into js.Dynamic
first, and accessing it in a dynamic manner. To facilitate the process, ScopeAware
provides scope.dynamic
method, which returns a dynamic version of the same scope
variable.
Alternatively, you can rewrite the above example in a more compact form as follows :
@JSExport
object UserDetailsController extends Controller {
override def initialize(scope : ScopeType) {
val future: Future[User] = http.get("/users/john")
future onComplete {
case Success(user) => {
scope.id = user.id
scope.name = user.name
scope.email = user.email
scope.friends = user.friends
}
case Failure(t) => println("An error has occured: " + t.getMessage)
}
}
@JSExport
def delete() {
userService.delete(scope.id)
}
trait ScopeType extends Scope {
var id: String
var name: String
var email: String
var friends: js.Array[String]
}
}
In this case, you can refer to the delete method from your template as controller.delete().
To define a directive, you can declare an object which implements Directive
trait.
You can also mixin such traits as ElementDirective
, AttributeDirective
,
Requires
, and so on to assign more specific behaviors to your directive implementation.
Scope related configuration can also be specified by mixing in one of InheritParentScope
,
UseParentScope
, or IsolatedScope
traits.
IsolatedScope
also provides its own DSL to specify attribute bindings, as specified by
AngularJS API:
object CustomerDirective extends ElementDirective
with TemplatedDirective with IsolatedScope {
override val name = "myCustomer"
override val templateUrl = "my-customer-iso.html"
bindings ++= Seq(
"customerInfo" := "info",
"title" :@ "",
"close" :& "onClose"
)
}
To implement a directive which manipulates DOM elements, you can override the link
method as follows:
object LocationDirective extends AttributeDirective {
override val name = "currentLocation"
@inject
var location: Location = _
override def link(scope: ScopeType, elems: Seq[Element], attrs: Attributes) {
val elem = elems.head.asInstanceOf[HTMLElement]
elem.innerHTML = location.path
}
}
Defining routing rules is quite straight forward, like the following example:
object RoutingConfig extends RouteProvider {
@inject
var routeProvider: RouteProvider = _
override def initialize() {
routeProvider
.when("/", Route("/assets/templates/home.html", "Home"))
.when("/signup", Route(SignUpController))
.when("/users", Route(UserListController))
}
}
Note that you can make your controller implement the PageController
trait and
register it directly to the routeProvider
as shown above.
There's an example implementation of TodoMvc application as a separate project:
This project is provided under the terms of Apache License, Version 2.0.