Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize DI using AnnotatedFunction #31

Merged
merged 11 commits into from
Feb 16, 2015
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import biz.enef.angular._
import biz.enef.angular.core.HttpService
import biz.enef.angular.ext.{Route, RouteProvider}

val module = Angular.module("app", Seq("ui.bootstrap","ngRoute"))
val module = angular.createModule("app", Seq("ui.bootstrap","ngRoute"))

module.serviceOf[UserService]
// - or, setting the service name explicitly:
Expand Down Expand Up @@ -86,7 +86,7 @@ Classes extending `Controller` export all their public `var`s, `val`s and `def`s
Defining a custom `Scope` type is not required. However, instantiating the controller in the template requires the new AngularJS `as` syntax:

```scala
val module Angular.module("counter", Nil)
val module = angular.createModule("counter")
module.controllerOf[CounterCtrl]

class CounterCtrl extends Controller {
Expand Down Expand Up @@ -134,7 +134,7 @@ class Ctrl($scope: Scope) extends Controller {
Classes extending `ScopeController` are "old-style" AngularJS controllers, where the scope available in the template must be injected explicitly:

```scala
val module Angular.module("counter", Nil)
val module = angular.createModule("counter")
module.controllerOf[CounterCtrl]

/* Option A: using a custom defined Scope type */
Expand Down Expand Up @@ -195,7 +195,7 @@ class UserCtrl(@named("$http") httpService: HttpService) extends Controller {
```


DI is also supported for functions passed to `Module.config()` and `Module.run()`:
DI is also supported for functions passed to methods such as `Module.config()` and `Module.run()`:
```scala
module.config( ($routeProvider: RouteProvider) => {
/* ... */
Expand All @@ -206,7 +206,17 @@ def routing($routeProvider: RouteProvider) = $routeProvider.when( /* ... */ )

module.config( routing _ )
```
However, the `@named` annoation is not supported for function DI, i.e. the _parameter names must match the services_ to be injected for this to work.
However, the `@named` annotation is not supported for function DI, i.e. the _parameter names must match the services_ to be injected for this to work.

Functions are implicitely converted to AnnotatedFunction which transform the function into an array following the [Inline Array Annotation](https://docs.angularjs.org/guide/di).
You can use this to declare the function in one place and use it in another:
```scala
val configFn: AnnotatedFunction = ($logProvider: js.Dynamic) => {
/* ... */
}

val module = angular.createModule("app", Nil, configFn)
```

### Services
Services can be implemented as plain classes extending the `Service` trait. As with controllers,
Expand Down
18 changes: 13 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ lazy val commonSettings = Seq(
version := "0.2-SNAPSHOT",
scalaVersion := "2.11.5",
scalacOptions ++= Seq("-deprecation","-feature","-Xlint"),
// work around for a bug during publishing
scalacOptions in (Compile,doc) ~= { _.filterNot(_.contains("scalajs-compiler_")) }
autoCompilerPlugins := true,
addCompilerPlugin("com.lihaoyi" %% "acyclic" % "0.1.2"),
libraryDependencies += "com.lihaoyi" %% "acyclic" % "0.1.2" % "provided",
scalacOptions ++= (if (isSnapshot.value) Seq.empty else Seq({
val a = baseDirectory.value.toURI.toString.replaceFirst("[^/]+/?$", "")
val g = "https://raw.githubusercontent.com/jokade/scalajs-angulate"
s"-P:scalajs:mapSourceURI:$a->$g/v${version.value}/"
}))
)


Expand All @@ -19,7 +25,7 @@ lazy val root = project.in(file(".")).
name := "scalajs-angulate",
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"org.scala-js" %%% "scalajs-dom" % "0.7.0"
"org.scala-js" %%% "scalajs-dom" % "0.8.0"
),
resolvers += Resolver.sonatypeRepo("releases")
)
Expand All @@ -29,11 +35,13 @@ lazy val tests = project.
dependsOn(root).
enablePlugins(ScalaJSPlugin).
settings(commonSettings: _*).
settings(utest.jsrunner.Plugin.utestJsSettings: _*).
settings(
publish := {},
scalacOptions ++= angulateDebugFlags,
scalaJSStage := FastOptStage,
scalaJSStage in Test := FastOptStage,
testFrameworks += new TestFramework("utest.runner.Framework"),
requiresDOM := true,
libraryDependencies += "com.lihaoyi" %%% "utest" % "0.3.0" % "test",
jsDependencies += RuntimeDOM,
jsDependencies += "org.webjars" % "angularjs" % "1.3.8" / "angular.min.js" % "test",
jsDependencies += "org.webjars" % "angularjs" % "1.3.8" / "angular-mocks.js" % "test"
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.13.6
sbt.version=0.13.7
4 changes: 1 addition & 3 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.0-RC2")

addSbtPlugin("com.lihaoyi" % "utest-js-plugin" % "0.2.5-RC1")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.0")
83 changes: 70 additions & 13 deletions src/main/scala/biz/enef/angular/Angular.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
// Distributed under the MIT License (see included file LICENSE)
package biz.enef.angular

import acyclic.file
import biz.enef.angular.Module.RichModule
import biz.enef.angular.core.Injector

import scala.language.experimental.macros
import org.scalajs.dom.html.Element

import scala.scalajs.js

Expand All @@ -21,17 +22,38 @@ trait Angular extends js.Object {
def injector(modules: js.Any, strictDi: Boolean = false) : Injector = js.native

/**
* Creates or retrieves an Angular module.
* Retrieves an Angular module.
*
* @param name The name of the module to retrieve.
*
* @see [[https://docs.angularjs.org/api/ng/function/angular.module]]
*/
def module(name: String) : Module = js.native

/**
* Creates an Angular module.
*
* @param name The name of the module to create or retrieve.
* @param name The name of the module to create .
* @param requires Array with the names of other modules required by this module.
* If specified then a new module is being created. If unspecified then the
* module is being retrieved for further configuration.
*
* @see [[https://docs.angularjs.org/api/ng/function/angular.module]]
*/
def module(name: String, requires: js.Array[String]) : Module = js.native

/**
* Creates an Angular module.
*
* @param name The name of the module to create.
* @param requires Array with the names of other modules required by this module.
* If specified then a new module is being created. If unspecified then the
* module is being retrieved for further configuration.
* @param configFn Optional configuration function for the module.
*
* @see [[https://docs.angularjs.org/api/ng/function/angular.module]]
*/
def module(name: String, requires: js.Array[String] = null, configFn: js.Function = null) : Module = js.native
def module(name: String, requires: js.Array[String], configFn: js.Array[Any]) : Module = js.native

/**
* Serializes input into a JSON-formatted string.
Expand All @@ -50,21 +72,56 @@ trait Angular extends js.Object {
*/
def uppercase(string: String) : String = js.native

def bootstrap(element: Element, modules: js.Array[Any]): Injector = js.native

def bootstrap(element: Element, modules: js.Array[Any], config: AngularConfiguration): Injector = js.native

}

case class AngularConfiguration(strictDi: Boolean = false)

object Angular {

/**
* Returns the global Angular object
*/
def apply() : Angular = js.Dynamic.global.angular.asInstanceOf[Angular] //macro impl.AngularImpl.apply
def apply() : Angular = js.Dynamic.global.angular.asInstanceOf[Angular]

/**
* Creates a new Angular
* @param name
* @param requires
* @return
*/
def module(name: String, requires: Iterable[String]) : Module = macro impl.AngularImpl.module
@inline final implicit class RichAngular(val self: Angular) extends AnyVal {
import scala.scalajs.js.JSConverters._

/**
* Creates a new Angular module
* @param name
* @return
*/
@inline def createModule(name: String) : RichModule = self.module(name, js.Array())

/**
* Creates a new Angular module
* @param name
* @param requires
* @return
*/
@inline def createModule(name: String, requires: Iterable[String]) : RichModule = self.module(name, requires.toJSArray)

/**
* Creates a new Angular module
* @param name
* @param requires
* @return
*/
@inline def createModule(name: String, requires: Iterable[String], configFn: AnnotatedFunction) : RichModule =
self.module(name, requires.toJSArray, configFn.inlineArrayAnnotatedFn)

@inline def bootstrap(element: Element, modules: Iterable[String]) = self.bootstrap(element, modules.toJSArray.asInstanceOf[js.Array[Any]])

@inline def bootstrap(element: Element, modules: Seq[AnnotatedFunction]) = self.bootstrap(element, modules.map(_.inlineArrayAnnotatedFn).toJSArray.asInstanceOf[js.Array[Any]])

@inline def bootstrap(element: Element, modules: Iterable[String], config: AngularConfiguration) = self.bootstrap(element, modules.toJSArray.asInstanceOf[js.Array[Any]], config)

@inline def bootstrap(element: Element, modules: Seq[AnnotatedFunction], config: AngularConfiguration) = self.bootstrap(element, modules.map(_.inlineArrayAnnotatedFn).toJSArray.asInstanceOf[js.Array[Any]], config)

}

}
24 changes: 24 additions & 0 deletions src/main/scala/biz/enef/angular/AnnotatedFunction.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package biz.enef.angular

import scala.scalajs.js
import scala.language.implicitConversions

@inline class AnnotatedFunction(val inlineArrayAnnotatedFn: js.Array[Any]) extends AnyVal

object AnnotatedFunction {

import scala.language.experimental.macros

@inline implicit def annotatedFunction(f: Function0[Any]): AnnotatedFunction = macro impl.AnnotationMacros.functionDIArray

@inline implicit def annotatedFunction(f: Function1[Nothing, Any]): AnnotatedFunction = macro impl.AnnotationMacros.functionDIArray

@inline implicit def annotatedFunction(f: Function2[Nothing, Nothing, Any]): AnnotatedFunction = macro impl.AnnotationMacros.functionDIArray

@inline implicit def annotatedFunction(f: Function3[Nothing, Nothing, Nothing, Any]): AnnotatedFunction = macro impl.AnnotationMacros.functionDIArray

@inline implicit def annotatedFunction(f: Function4[Nothing, Nothing, Nothing, Nothing, Any]): AnnotatedFunction = macro impl.AnnotationMacros.functionDIArray

@inline implicit def annotatedFunction(f: Function5[Nothing, Nothing, Nothing, Nothing, Nothing, Any]): AnnotatedFunction = macro impl.AnnotationMacros.functionDIArray

}
1 change: 1 addition & 0 deletions src/main/scala/biz/enef/angular/Controller.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Distributed under the MIT License (see included file LICENSE)
package biz.enef.angular

import acyclic.file
import biz.enef.angular.core.{Attributes, JQLite}

import scala.scalajs.js
Expand Down
1 change: 1 addition & 0 deletions src/main/scala/biz/enef/angular/Directive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Distributed under the MIT License (see included file LICENSE)
package biz.enef.angular

import acyclic.file
import biz.enef.angular.core.{Attributes, JQLite}

import scala.scalajs.js
Expand Down
Loading