You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by mo...@apache.org on 2016/02/01 02:58:22 UTC

incubator-zeppelin git commit: Utilities for Angular Display system

Repository: incubator-zeppelin
Updated Branches:
  refs/heads/master e6a845fc2 -> 602a1e78c


Utilities for Angular Display system

### What is this PR for?
[Angular display system](http://zeppelin.incubator.apache.org/docs/0.6.0-incubating-SNAPSHOT/displaysystem/angular.html) provides way to Interpreter interact with front-end.

However, the api was pretty much low level and not very much intuitive, it was difficult to make readable code. This PR provides simple intuitive API for using angular display system by leveraging scala.xml. Following is usage

Import

```scala
// notebook scope
import org.apache.zeppelin.display.angular.notebookscope._
import AngularElem._
```

or

```scala
// paragraph scope
import org.apache.zeppelin.display.angular.paragraphscope._
import AngularElem._
```

Display element

```scala
// automatically convert to string and print with %angular display system directive in front.
<div><div>.display
```

Event handler

```scala
// on click
<div></div>.onClick(() => {
   my callback routine
}).display

// on change
<div></div>.onChange(() => {
  my callback routine
}).display

// arbitrary event
<div></div>.onEvent("ng-click", () => {
  my callback routine
}).display
```

Bind model

```scala
// bind model
<div></div>.model("myModel").display

// bind model with initial value
<div></div>.model("myModel", initialValue).display
```

Interact with model
```scala
// read model
AngularModel("myModel")()

// update model
AngularModel("myModel", "newValue")
```

### What type of PR is it?
Feature

### Todos

### Is there a relevant Jira issue?

### How should this be tested?
See usage and screenshot

### Screenshots (if appropriate)

Example of basic usage
![image](https://cloud.githubusercontent.com/assets/1540981/12075925/03725cd4-b148-11e5-9e03-04ebb9522c6d.png)

Example of string converter
![display_util](https://cloud.githubusercontent.com/assets/1540981/12075929/5142bbd4-b148-11e5-8b65-a5bfecf39566.gif)

### Questions:
* Does the licenses files need update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? documentation will follow

Author: Lee moon soo <mo...@apache.org>

Closes #591 from Leemoonsoo/display_utils and squashes the following commits:

9195021 [Lee moon soo] Remove paragraph scope angular object on paragraph removal and add unittest
f1fb769 [Lee moon soo] provide two different packages for notebook scope and paragraph scope
c181cbf [Lee moon soo] Merge branch 'master' into display_utils
e3b6812 [Lee moon soo] Update angular display utils display output to InterpreterOutput instead of Console.out by default
bdf0e54 [Lee moon soo] Merge branch 'master' into display_utils
3268a9d [Lee moon soo] update travis
f7b0d9c [Lee moon soo] zeppelin-display-utils -> zeppelin-display
e0ce18f [Lee moon soo] set InterpreterContext for callback function
f14b0ff [Lee moon soo] AngularModel
819b7c3 [Lee moon soo] add display method
fa4e72b [Lee moon soo] Using onEvent based on AngularObject
991fd1f [Lee moon soo] fix test
777ba16 [Lee moon soo] Initial commit of zeppelin-display-utils


Project: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/commit/602a1e78
Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/602a1e78
Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/602a1e78

Branch: refs/heads/master
Commit: 602a1e78c30e3b2da26305a23483e583f6e73b8e
Parents: e6a845f
Author: Lee moon soo <mo...@apache.org>
Authored: Sat Jan 23 15:54:52 2016 -0800
Committer: Lee moon soo <mo...@apache.org>
Committed: Mon Feb 1 11:01:11 2016 +0900

----------------------------------------------------------------------
 pom.xml                                         |   1 +
 spark/pom.xml                                   |   6 +
 zeppelin-display/pom.xml                        | 187 ++++++++++++++++++
 .../display/angular/AbstractAngularElem.scala   | 192 +++++++++++++++++++
 .../display/angular/AbstractAngularModel.scala  |  95 +++++++++
 .../angular/notebookscope/AngularElem.scala     |  84 ++++++++
 .../angular/notebookscope/AngularModel.scala    |  52 +++++
 .../angular/paragraphscope/AngularElem.scala    |  86 +++++++++
 .../angular/paragraphscope/AngularModel.scala   |  53 +++++
 .../angular/AbstractAngularElemTest.scala       | 154 +++++++++++++++
 .../angular/AbstractAngularModelTest.scala      |  97 ++++++++++
 .../angular/notebookscope/AngularElemTest.scala |  41 ++++
 .../notebookscope/AngularModelTest.scala        |  32 ++++
 .../paragraphscope/AngularElemTest.scala        |  41 ++++
 .../paragraphscope/AngularModelTest.scala       |  32 ++++
 .../java/org/apache/zeppelin/notebook/Note.java |   9 +-
 .../org/apache/zeppelin/notebook/Notebook.java  |  10 +
 .../apache/zeppelin/notebook/NotebookTest.java  |  51 ++++-
 18 files changed, 1217 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index d73caf3..9d46c0d 100755
--- a/pom.xml
+++ b/pom.xml
@@ -86,6 +86,7 @@
   <modules>
     <module>zeppelin-interpreter</module>
     <module>zeppelin-zengine</module>
+    <module>zeppelin-display</module>
     <module>spark-dependencies</module>
     <module>spark</module>
     <module>markdown</module>

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/spark/pom.xml
----------------------------------------------------------------------
diff --git a/spark/pom.xml b/spark/pom.xml
index 773bbc9..a1c28af 100644
--- a/spark/pom.xml
+++ b/spark/pom.xml
@@ -54,6 +54,12 @@
 
     <dependency>
       <groupId>${project.groupId}</groupId>
+      <artifactId>zeppelin-display</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>${project.groupId}</groupId>
       <artifactId>zeppelin-interpreter</artifactId>
       <version>${project.version}</version>
     </dependency>

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/pom.xml
----------------------------------------------------------------------
diff --git a/zeppelin-display/pom.xml b/zeppelin-display/pom.xml
new file mode 100644
index 0000000..1a8328e
--- /dev/null
+++ b/zeppelin-display/pom.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>zeppelin</artifactId>
+    <groupId>org.apache.zeppelin</groupId>
+    <version>0.6.0-incubating-SNAPSHOT</version>
+    <relativePath>..</relativePath>
+  </parent>
+
+  <groupId>org.apache.zeppelin</groupId>
+  <artifactId>zeppelin-display</artifactId>
+  <packaging>jar</packaging>
+  <version>0.6.0-incubating-SNAPSHOT</version>
+  <name>Zeppelin: Display system apis</name>
+  <url>http://incubator.zeppelin.apache.org</url>
+
+  <properties>
+    <scala.version>2.10.4</scala.version>
+    <scala.binary.version>2.10</scala.binary.version>
+  </properties>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.scala-lang</groupId>
+        <artifactId>scala-library</artifactId>
+        <version>${scala.version}</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.scala-lang</groupId>
+        <artifactId>scala-compiler</artifactId>
+        <version>${scala.version}</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.scala-lang</groupId>
+        <artifactId>scalap</artifactId>
+        <version>${scala.version}</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>zeppelin-interpreter</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.scala-lang</groupId>
+      <artifactId>scala-library</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.scalatest</groupId>
+      <artifactId>scalatest_2.10</artifactId>
+      <version>2.1.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <configuration>
+          <excludes>
+            <exclude>**/.idea/</exclude>
+            <exclude>**/*.iml</exclude>
+            <exclude>.git/</exclude>
+            <exclude>.gitignore</exclude>
+            <exclude>**/.settings/*</exclude>
+            <exclude>**/.classpath</exclude>
+            <exclude>**/.project</exclude>
+            <exclude>**/target/**</exclude>
+            <exclude>**/README.md</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-failsafe-plugin</artifactId>
+        <version>2.16</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>integration-test</goal>
+              <goal>verify</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <argLine>-Xmx2048m</argLine>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <version>2.7</version>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.scala-tools</groupId>
+        <artifactId>maven-scala-plugin</artifactId>
+        <version>2.15.2</version>
+        <executions>
+          <execution>
+            <id>compile</id>
+            <goals>
+              <goal>compile</goal>
+            </goals>
+            <phase>compile</phase>
+          </execution>
+          <execution>
+            <id>test-compile</id>
+            <goals>
+              <goal>testCompile</goal>
+            </goals>
+            <phase>test-compile</phase>
+          </execution>
+          <execution>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>compile</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.scalatest</groupId>
+        <artifactId>scalatest-maven-plugin</artifactId>
+        <version>1.0</version>
+        <executions>
+          <execution>
+            <id>test</id>
+            <goals>
+              <goal>test</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/AbstractAngularElem.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/AbstractAngularElem.scala b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/AbstractAngularElem.scala
new file mode 100644
index 0000000..80e3699
--- /dev/null
+++ b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/AbstractAngularElem.scala
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.display.angular
+
+import java.io.PrintStream
+
+import org.apache.zeppelin.display.{AngularObjectWatcher, AngularObject}
+import org.apache.zeppelin.interpreter.{InterpreterResult, InterpreterContext}
+
+import scala.xml._
+
+/**
+  * Element that binded to Angular object
+  */
+abstract class AbstractAngularElem(val interpreterContext: InterpreterContext,
+                                   val modelName: String,
+                                   val angularObjects: Map[String, AngularObject[Any]],
+                                   prefix: String,
+                                   label: String,
+                                   attributes1: MetaData,
+                                   scope: NamespaceBinding,
+                                   minimizeEmpty: Boolean,
+                                   child: Node*)
+  extends Elem(prefix, label, attributes1, scope, minimizeEmpty, child:_*) {
+
+  val uniqueId = java.util.UUID.randomUUID.toString.replaceAll("-", "_")
+
+  /**
+    * On click element
+    * @param callback
+    * @return
+    */
+  def onClick(callback: () => Unit): AbstractAngularElem = {
+    onEvent("ng-click", callback)
+  }
+
+  /**
+    * On
+    * @param callback
+    * @return
+    */
+  def onChange(callback: () => Unit): AbstractAngularElem = {
+    onEvent("ng-change", callback)
+  }
+
+  /**
+    * Bind angularObject to ng-model directive
+    * @param name name of angularObject
+    * @param value initialValue
+    * @return
+    */
+  def model(name: String, value: Any): AbstractAngularElem = {
+    val registry = interpreterContext.getAngularObjectRegistry
+
+    // create AngularFunction in current paragraph
+    val elem = this % Attribute(None, "ng-model",
+      Text(s"${name}"),
+      Null)
+
+    val angularObject = addAngularObject(name, value)
+      .asInstanceOf[AngularObject[Any]]
+
+    newElem(
+      interpreterContext,
+      name,
+      angularObjects + (name -> angularObject),
+      elem)
+  }
+
+
+  def model(name: String): AbstractAngularElem = {
+    val registry = interpreterContext.getAngularObjectRegistry
+
+    // create AngularFunction in current paragraph
+    val elem = this % Attribute(None, "ng-model",
+      Text(s"${name}"),
+      Null)
+
+    newElem(
+      interpreterContext,
+      name,
+      angularObjects,
+      elem)
+  }
+
+  /**
+    * Retrieve value of model
+    * @return
+    */
+  def model(): Any = {
+    if (angularObjects.contains(modelName)) {
+      angularObjects(modelName).get()
+    } else {
+      None
+    }
+  }
+
+  /**
+    *
+    * @param eventName angular directive like ng-click, ng-change, etc.
+    * @return
+    */
+  def onEvent(eventName: String, callback: () => Unit): AbstractAngularElem = {
+    val registry = interpreterContext.getAngularObjectRegistry
+
+    // create AngularFunction in current paragraph
+    val functionName = eventName.replaceAll("-", "_") + "_" + uniqueId
+    val elem = this % Attribute(None, eventName,
+      Text(s"${functionName}=$$event.timeStamp"),
+      Null)
+
+    val angularObject = addAngularObject(functionName, "")
+
+    angularObject.addWatcher(new AngularObjectWatcher(interpreterContext) {
+      override def watch(oldObject: scala.Any, newObject: scala.Any, context: InterpreterContext)
+      :Unit = {
+        InterpreterContext.set(interpreterContext)
+        callback()
+      }
+    })
+
+    newElem(
+      interpreterContext,
+      modelName,
+      angularObjects + (eventName -> angularObject),
+      elem)
+  }
+
+  protected def addAngularObject(name: String, value: Any): AngularObject[Any]
+
+  protected def newElem(interpreterContext: InterpreterContext,
+                        name: String,
+                        angularObjects: Map[String, AngularObject[Any]],
+                        elem: scala.xml.Elem): AbstractAngularElem
+
+  /**
+    * disassociate this element and it's child from front-end
+    * by removing angularobject
+    */
+  def disassociate() = {
+    remove(this)
+  }
+
+  /**
+    * Remove all angularObject recursively
+    * @param node
+    */
+  private def remove(node: Node): Unit = {
+    if (node.isInstanceOf[AbstractAngularElem]) {
+      node.asInstanceOf[AbstractAngularElem].angularObjects.values.foreach{ ao =>
+        interpreterContext.getAngularObjectRegistry.remove(ao.getName, ao.getNoteId, ao
+          .getParagraphId)
+      }
+    }
+
+    node.child.foreach(remove _)
+  }
+
+  /**
+    * Print into provided print stream
+    * @return
+    */
+  def display(out: java.io.PrintStream): Unit = {
+    out.print(this.toString)
+    out.flush()
+  }
+
+  /**
+    * Print into InterpreterOutput
+    */
+  def display(): Unit = {
+    val out = interpreterContext.out
+    out.setType(InterpreterResult.Type.ANGULAR)
+    out.write(this.toString())
+    out.flush()
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/AbstractAngularModel.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/AbstractAngularModel.scala b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/AbstractAngularModel.scala
new file mode 100644
index 0000000..ff50438
--- /dev/null
+++ b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/AbstractAngularModel.scala
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.display.angular
+
+import org.apache.zeppelin.display.AngularObject
+import org.apache.zeppelin.interpreter.InterpreterContext
+
+/**
+  * Represents ng-model with angular object
+  */
+abstract class AbstractAngularModel(name: String) {
+  val context = InterpreterContext.get
+  val registry = context.getAngularObjectRegistry
+
+
+  /**
+    * Create AngularModel with initial Value
+    * @param name name of model
+    * @param newValue value
+    */
+  def this(name: String, newValue: Any) = {
+    this(name)
+    value(newValue)
+  }
+
+  protected def getAngularObject(): AngularObject[Any]
+  protected def addAngularObject(value: Any): AngularObject[Any]
+
+  /**
+    * Get value of the model
+    * @return
+    */
+  def apply(): Any = {
+    value()
+  }
+
+  /**
+    * Get value of the model
+    * @return
+    */
+  def value(): Any = {
+    val angularObject = getAngularObject()
+    if (angularObject == null) {
+      None
+    } else {
+      angularObject.get
+    }
+  }
+
+
+  def apply(newValue: Any): Unit = {
+    value(newValue)
+  }
+
+
+  /**
+    * Set value of the model
+    * @param newValue
+    */
+  def value(newValue: Any): Unit = {
+    var angularObject = getAngularObject()
+    if (angularObject == null) {
+      // create new object
+      angularObject = addAngularObject(newValue)
+    } else {
+      angularObject.set(newValue)
+    }
+    angularObject.get()
+  }
+
+  def remove(): Any = {
+    val angularObject = getAngularObject()
+
+    if (angularObject == null) {
+      None
+    } else {
+      registry.remove(name, angularObject.getNoteId(), angularObject.getParagraphId())
+      angularObject.get
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/notebookscope/AngularElem.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/notebookscope/AngularElem.scala b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/notebookscope/AngularElem.scala
new file mode 100644
index 0000000..9ba88ff
--- /dev/null
+++ b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/notebookscope/AngularElem.scala
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.display.angular.notebookscope
+
+import org.apache.zeppelin.display.angular.AbstractAngularElem
+import org.apache.zeppelin.display.{angular, AngularObject}
+import org.apache.zeppelin.interpreter.InterpreterContext
+
+import scala.collection.JavaConversions
+import scala.xml._
+
+/**
+  * AngularElement in notebook scope
+  */
+class AngularElem(override val interpreterContext: InterpreterContext,
+                  override val modelName: String,
+                  override val angularObjects: Map[String, AngularObject[Any]],
+                  prefix: String,
+                  label: String,
+                  attributes1: MetaData,
+                  scope: NamespaceBinding,
+                  minimizeEmpty: Boolean,
+                  child: Node*)
+  extends AbstractAngularElem(
+    interpreterContext, modelName, angularObjects, prefix, label, attributes1, scope,
+    minimizeEmpty, child: _*) {
+
+  override protected def addAngularObject(name: String, value: Any): AngularObject[Any] = {
+    val registry = interpreterContext.getAngularObjectRegistry
+    registry.add(name, value, interpreterContext.getNoteId, null).asInstanceOf[AngularObject[Any]]
+
+  }
+
+  override protected def newElem(interpreterContext: InterpreterContext,
+                                 name: String,
+                                 angularObjects: Map[String, AngularObject[Any]],
+                                 elem: scala.xml.Elem): angular.AbstractAngularElem = {
+    new AngularElem(
+      interpreterContext,
+      name,
+      angularObjects,
+      elem.prefix,
+      elem.label,
+      elem.attributes,
+      elem.scope,
+      elem.minimizeEmpty,
+      elem.child:_*)
+  }
+}
+
+object AngularElem {
+  implicit def Elem2AngularDisplayElem(elem: Elem): AbstractAngularElem = {
+    new AngularElem(InterpreterContext.get(), null,
+      Map[String, AngularObject[Any]](),
+      elem.prefix, elem.label, elem.attributes, elem.scope, elem.minimizeEmpty, elem.child:_*);
+  }
+
+  /**
+    * Disassociate (remove) all angular object in this notebook
+    */
+  def disassociate() = {
+    val ic = InterpreterContext.get
+    val registry = ic.getAngularObjectRegistry
+
+    JavaConversions.asScalaBuffer(registry.getAll(ic.getNoteId, null)).foreach(ao =>
+      registry.remove(ao.getName, ao.getNoteId, null)
+    )
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/notebookscope/AngularModel.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/notebookscope/AngularModel.scala b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/notebookscope/AngularModel.scala
new file mode 100644
index 0000000..1ef8983
--- /dev/null
+++ b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/notebookscope/AngularModel.scala
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.display.angular.notebookscope
+
+import org.apache.zeppelin.display.AngularObject
+import org.apache.zeppelin.display.angular.AbstractAngularModel
+import org.apache.zeppelin.interpreter.InterpreterContext
+
+/**
+  * Represents ng-model in notebook scope
+  */
+class AngularModel(name: String)
+  extends org.apache.zeppelin.display.angular.AbstractAngularModel(name) {
+
+  def this(name: String, newValue: Any) = {
+    this(name)
+    value(newValue)
+  }
+
+  override protected def getAngularObject(): AngularObject[Any] = {
+    registry.get(name, context.getNoteId, null).asInstanceOf[AngularObject[Any]]
+  }
+
+  override protected def addAngularObject(value: Any): AngularObject[Any] = {
+    registry.add(name, value, context.getNoteId, null).asInstanceOf[AngularObject[Any]]
+  }
+}
+
+
+object AngularModel {
+  def apply(name: String): AbstractAngularModel = {
+    new AngularModel(name)
+  }
+
+  def apply(name: String, newValue: Any): AbstractAngularModel = {
+    new AngularModel(name, newValue)
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularElem.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularElem.scala b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularElem.scala
new file mode 100644
index 0000000..50bd0ed
--- /dev/null
+++ b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularElem.scala
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.display.angular.paragraphscope
+
+
+import org.apache.zeppelin.display.angular.AbstractAngularElem
+import org.apache.zeppelin.display.{angular, AngularObject}
+import org.apache.zeppelin.interpreter.InterpreterContext
+
+import scala.collection.JavaConversions
+import scala.xml._
+
+/**
+  * AngularElement in paragraph scope
+  */
+class AngularElem(override val interpreterContext: InterpreterContext,
+                  override val modelName: String,
+                  override val angularObjects: Map[String, AngularObject[Any]],
+                  prefix: String,
+                  label: String,
+                  attributes1: MetaData,
+                  scope: NamespaceBinding,
+                  minimizeEmpty: Boolean,
+                  child: Node*)
+  extends AbstractAngularElem(
+    interpreterContext, modelName, angularObjects, prefix, label, attributes1, scope,
+    minimizeEmpty, child: _*) {
+
+  override protected def addAngularObject(name: String, value: Any): AngularObject[Any] = {
+    val registry = interpreterContext.getAngularObjectRegistry
+    registry.add(name, value, interpreterContext.getNoteId, interpreterContext.getParagraphId)
+      .asInstanceOf[AngularObject[Any]]
+
+  }
+
+  override protected def newElem(interpreterContext: InterpreterContext,
+                                 name: String,
+                                 angularObjects: Map[String, AngularObject[Any]],
+                                 elem: scala.xml.Elem): angular.AbstractAngularElem = {
+    new AngularElem(
+      interpreterContext,
+      name,
+      angularObjects,
+      elem.prefix,
+      elem.label,
+      elem.attributes,
+      elem.scope,
+      elem.minimizeEmpty,
+      elem.child:_*)
+  }
+}
+
+object AngularElem {
+  implicit def Elem2AngularDisplayElem(elem: Elem): AbstractAngularElem = {
+    new AngularElem(InterpreterContext.get(), null,
+      Map[String, AngularObject[Any]](),
+      elem.prefix, elem.label, elem.attributes, elem.scope, elem.minimizeEmpty, elem.child:_*);
+  }
+
+  /**
+    * Disassociate (remove) all angular object in this notebook
+    */
+  def disassociate() = {
+    val ic = InterpreterContext.get
+    val registry = ic.getAngularObjectRegistry
+
+    JavaConversions.asScalaBuffer(registry.getAll(ic.getNoteId, ic.getParagraphId)).foreach(ao =>
+      registry.remove(ao.getName, ao.getNoteId, ao.getParagraphId)
+    )
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularModel.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularModel.scala b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularModel.scala
new file mode 100644
index 0000000..ed28687
--- /dev/null
+++ b/zeppelin-display/src/main/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularModel.scala
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.display.angular.paragraphscope
+
+import org.apache.zeppelin.display.AngularObject
+import org.apache.zeppelin.display.angular.AbstractAngularModel
+
+/**
+  * Represents ng-model in paragraph scope
+  */
+class AngularModel(name: String)
+  extends org.apache.zeppelin.display.angular.AbstractAngularModel(name) {
+
+  def this(name: String, newValue: Any) = {
+    this(name)
+    value(newValue)
+  }
+
+  override protected def getAngularObject(): AngularObject[Any] = {
+    registry.get(name,
+      context.getNoteId, context.getParagraphId).asInstanceOf[AngularObject[Any]]
+  }
+
+  override protected def addAngularObject(value: Any): AngularObject[Any] = {
+    registry.add(name, value,
+      context.getNoteId, context.getParagraphId).asInstanceOf[AngularObject[Any]]
+  }
+}
+
+
+object AngularModel {
+  def apply(name: String): AbstractAngularModel = {
+    new AngularModel(name)
+  }
+
+  def apply(name: String, newValue: Any): AbstractAngularModel = {
+    new AngularModel(name, newValue)
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularElemTest.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularElemTest.scala b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularElemTest.scala
new file mode 100644
index 0000000..2c82c8e
--- /dev/null
+++ b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularElemTest.scala
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.display.angular
+
+import java.io.{ByteArrayOutputStream, PrintStream}
+import java.util
+
+import org.apache.zeppelin.display.{AngularObject, AngularObjectRegistry, GUI}
+import org.apache.zeppelin.interpreter._
+import org.scalatest.concurrent.Eventually
+import org.scalatest.{BeforeAndAfter, BeforeAndAfterEach, FlatSpec, Matchers}
+
+/**
+  * Test
+  */
+trait AbstractAngularElemTest
+  extends FlatSpec with BeforeAndAfter with BeforeAndAfterEach with Eventually with Matchers {
+
+  override def beforeEach() {
+    val intpGroup = new InterpreterGroup()
+    val context = new InterpreterContext("note", "paragraph", "title", "text",
+      new util.HashMap[String, Object](), new GUI(), new AngularObjectRegistry(
+        intpGroup.getId(), null),
+      new util.LinkedList[InterpreterContextRunner](),
+      new InterpreterOutput(new InterpreterOutputListener() {
+        override def onAppend(out: InterpreterOutput, line: Array[Byte]): Unit = {
+          // nothing to do
+        }
+
+        override def onUpdate(out: InterpreterOutput, output: Array[Byte]): Unit = {
+          // nothing to do
+        }
+      }))
+
+    InterpreterContext.set(context)
+    super.beforeEach() // To be stackable, must call super.beforeEach
+  }
+
+  def angularElem(elem: scala.xml.Elem): AbstractAngularElem;
+  def angularModel(name: String): AbstractAngularModel;
+
+
+  "AngularElem" should "provide onclick method" in {
+    registrySize should be(0)
+
+    var a = 0
+    val elem = angularElem(<div></div>).onClick(() => {
+      a = a + 1
+    })
+    elem.angularObjects.get("ng-click") should not be(null)
+    registrySize should be(1)
+
+    // click create thread for callback function to run. So it'll may not immediately invoked
+    // after click. therefore eventually should be
+    click(elem)
+    eventually {
+      a should be(1)
+    }
+
+    click(elem)
+    eventually {
+      a should be(2)
+    }
+
+    // disassociate
+    elem.disassociate()
+    registrySize should be(0)
+  }
+
+  "AngularElem" should "print angular display directive only once in a paragraph" in {
+    val out = new ByteArrayOutputStream()
+    val printOut = new PrintStream(out)
+
+    angularElem(<div></div>).display(printOut)
+    out.toString should be("<div></div>")
+
+    out.reset
+    angularElem(<div></div>).display(printOut)
+    out.toString should be("<div></div>")
+  }
+
+  "AngularElem" should "bind angularObject to ng-model directive " in {
+    angularElem(<div></div>)
+      .model("name", "value").toString should be("<div ng-model=\"name\"></div>")
+    angularElem(<div></div>).model("name", "value").model() should be("value")
+    angularElem(<div></div>).model() should be(None)
+  }
+
+  "AngularElem" should "able to disassociate AngularObjects" in {
+    val elem1 = angularElem(<div></div>).model("name1", "value1")
+    val elem2 = angularElem(<div></div>).model("name2", "value2")
+    val elem3 = angularElem(<div></div>).model("name3", "value3")
+
+    registrySize should be(3)
+
+    elem1.disassociate()
+    registrySize should be(2)
+
+    elem2.disassociate()
+    elem3.disassociate()
+    registrySize should be(0)
+  }
+
+  "AngularElem" should "allow access to InterpreterContext inside of callback function" in {
+    angularModel("name").value("value")
+
+    var modelValue = ""
+
+    val elem = angularElem(<div></div>).onClick(() =>
+      modelValue = angularModel("name")().toString
+    )
+
+    click(elem)
+
+    eventually { modelValue should be("value")}
+  }
+
+
+  def registry = {
+    InterpreterContext.get().getAngularObjectRegistry
+  }
+
+  def registrySize = {
+    registry.getAllWithGlobal("note").size()
+  }
+
+  def noteId = {
+    InterpreterContext.get().getNoteId
+  }
+
+  def click(elem: org.apache.zeppelin.display.angular.AbstractAngularElem) = {
+    fireEvent("ng-click", elem)
+  }
+
+  // simulate click
+  def fireEvent(eventName: String, elem: org.apache.zeppelin.display.angular.AbstractAngularElem) = {
+    val angularObject: AngularObject[Any] = elem.angularObjects(eventName);
+    angularObject.set("event");
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularModelTest.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularModelTest.scala b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularModelTest.scala
new file mode 100644
index 0000000..942390a
--- /dev/null
+++ b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/AbstractAngularModelTest.scala
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.display.angular
+
+import org.apache.zeppelin.display.{AngularObjectRegistry, GUI}
+import org.apache.zeppelin.interpreter._
+import org.scalatest.concurrent.Eventually
+import org.scalatest.{BeforeAndAfter, BeforeAndAfterEach, FlatSpec, Matchers}
+
+/**
+  * Abstract Test for AngularModel
+  */
+trait AbstractAngularModelTest extends FlatSpec
+with BeforeAndAfter with BeforeAndAfterEach with Eventually with Matchers {
+  override def beforeEach() {
+    val intpGroup = new InterpreterGroup()
+    val context = new InterpreterContext("note", "id", "title", "text",
+      new java.util.HashMap[String, Object](), new GUI(), new AngularObjectRegistry(
+        intpGroup.getId(), null),
+      new java.util.LinkedList[InterpreterContextRunner](),
+      new InterpreterOutput(new InterpreterOutputListener() {
+        override def onAppend(out: InterpreterOutput, line: Array[Byte]): Unit = {
+          // nothing to do
+        }
+
+        override def onUpdate(out: InterpreterOutput, output: Array[Byte]): Unit = {
+          // nothing to do
+        }
+      }))
+
+    InterpreterContext.set(context)
+    super.beforeEach() // To be stackable, must call super.beforeEach
+  }
+
+  def angularModel(name: String): AbstractAngularModel
+  def angularModel(name: String, value: Any): AbstractAngularModel
+
+  "AngularModel" should "able to create AngularObject" in {
+    val registry = InterpreterContext.get().getAngularObjectRegistry
+    registrySize should be(0)
+
+    angularModel("model1")() should be(None)
+    registrySize should be(0)
+
+    angularModel("model1", "value1")() should be("value1")
+    registrySize should be(1)
+
+    angularModel("model1")() should be("value1")
+    registrySize should be(1)
+  }
+
+  "AngularModel" should "able to update AngularObject" in {
+    val registry = InterpreterContext.get().getAngularObjectRegistry
+
+    val model1 = angularModel("model1", "value1")
+    model1() should be("value1")
+    registrySize should be(1)
+
+    model1.value("newValue1")
+    model1() should be("newValue1")
+    registrySize should be(1)
+
+    angularModel("model1", "value2")() should be("value2")
+    registrySize should be(1)
+  }
+
+  "AngularModel" should "able to remove AngularObject" in {
+    angularModel("model1", "value1")
+    registrySize should be(1)
+
+    angularModel("model1").remove()
+    registrySize should be(0)
+  }
+
+
+  def registry() = {
+    InterpreterContext.get().getAngularObjectRegistry
+  }
+
+  def registrySize() = {
+    registry().getAllWithGlobal(InterpreterContext.get().getNoteId).size
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/notebookscope/AngularElemTest.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/notebookscope/AngularElemTest.scala b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/notebookscope/AngularElemTest.scala
new file mode 100644
index 0000000..a3effb0
--- /dev/null
+++ b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/notebookscope/AngularElemTest.scala
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.display.angular.notebookscope
+
+
+import org.apache.zeppelin.display.angular.{AbstractAngularElem, AbstractAngularModel, AbstractAngularElemTest}
+
+import scala.xml.Elem
+
+/**
+  * Test
+  */
+class AngularElemTest extends AbstractAngularElemTest {
+
+  override def angularElem(elem: Elem): AbstractAngularElem = {
+    AngularElem.Elem2AngularDisplayElem(elem)
+  }
+
+  override def angularModel(name: String): AbstractAngularModel = {
+    AngularModel(name)
+  }
+
+  "AngularElem" should "able to be created from implicit conversion" in {
+    import AngularElem._
+    <div></div>.model("modelname")
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/notebookscope/AngularModelTest.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/notebookscope/AngularModelTest.scala b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/notebookscope/AngularModelTest.scala
new file mode 100644
index 0000000..1019793
--- /dev/null
+++ b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/notebookscope/AngularModelTest.scala
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.display.angular.notebookscope
+
+import org.apache.zeppelin.display.angular.{AbstractAngularModel, AbstractAngularModelTest}
+
+/**
+  * Test for AngularModel
+  */
+class AngularModelTest extends AbstractAngularModelTest {
+  override def angularModel(name: String): AbstractAngularModel = {
+    AngularModel(name)
+  }
+
+  override def angularModel(name: String, value: Any): AbstractAngularModel = {
+    AngularModel(name, value)
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularElemTest.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularElemTest.scala b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularElemTest.scala
new file mode 100644
index 0000000..4135ded
--- /dev/null
+++ b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularElemTest.scala
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.display.angular.paragraphscope
+
+
+import org.apache.zeppelin.display.angular.{AbstractAngularElem, AbstractAngularModel, AbstractAngularElemTest}
+
+import scala.xml.Elem
+
+/**
+  * Test
+  */
+class AngularElemTest extends AbstractAngularElemTest {
+
+  override def angularElem(elem: Elem): AbstractAngularElem = {
+    AngularElem.Elem2AngularDisplayElem(elem)
+  }
+
+  override def angularModel(name: String): AbstractAngularModel = {
+    AngularModel(name)
+  }
+
+  "AngularElem" should "able to be created from implicit conversion" in {
+    import AngularElem._
+    <div></div>.model("modelname")
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularModelTest.scala
----------------------------------------------------------------------
diff --git a/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularModelTest.scala b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularModelTest.scala
new file mode 100644
index 0000000..c6e1eb0
--- /dev/null
+++ b/zeppelin-display/src/test/scala/org/apache/zeppelin/display/angular/paragraphscope/AngularModelTest.scala
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.display.angular.paragraphscope
+
+import org.apache.zeppelin.display.angular.{AbstractAngularModel, AbstractAngularModelTest}
+
+/**
+  * Test for AngularModel
+  */
+class AngularModelTest extends AbstractAngularModelTest {
+  override def angularModel(name: String): AbstractAngularModel = {
+    AngularModel(name)
+  }
+
+  override def angularModel(name: String, value: Any): AbstractAngularModel = {
+    AngularModel(name, value)
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
index 27e2f77..52e7ea3 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
@@ -29,6 +29,7 @@ import org.apache.zeppelin.display.AngularObject;
 import org.apache.zeppelin.display.AngularObjectRegistry;
 import org.apache.zeppelin.display.Input;
 import org.apache.zeppelin.interpreter.*;
+import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry;
 import org.apache.zeppelin.notebook.repo.NotebookRepo;
 import org.apache.zeppelin.notebook.utility.IdHashes;
 import org.apache.zeppelin.scheduler.Job;
@@ -413,7 +414,13 @@ public class Note implements Serializable, JobListener {
     for (InterpreterSetting setting : settings) {
       InterpreterGroup intpGroup = setting.getInterpreterGroup();
       AngularObjectRegistry registry = intpGroup.getAngularObjectRegistry();
-      registry.removeAll(id, paragraphId);
+
+      if (registry instanceof RemoteAngularObjectRegistry) {
+        // remove paragraph scope object
+        ((RemoteAngularObjectRegistry) registry).removeAllAndNotifyRemoteProcess(id, paragraphId);
+      } else {
+        registry.removeAll(id, paragraphId);
+      }
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java
index cae4210..7a87c92 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Notebook.java
@@ -284,8 +284,18 @@ public class Notebook {
     for (InterpreterSetting settings : replFactory.get()) {
       AngularObjectRegistry registry = settings.getInterpreterGroup().getAngularObjectRegistry();
       if (registry instanceof RemoteAngularObjectRegistry) {
+        // remove paragraph scope object
+        for (Paragraph p : note.getParagraphs()) {
+          ((RemoteAngularObjectRegistry) registry).removeAllAndNotifyRemoteProcess(id, p.getId());
+        }
+        // remove notebook scope object
         ((RemoteAngularObjectRegistry) registry).removeAllAndNotifyRemoteProcess(id, null);
       } else {
+        // remove paragraph scope object
+        for (Paragraph p : note.getParagraphs()) {
+          registry.removeAll(id, p.getId());
+        }
+        // remove notebook scope object
         registry.removeAll(id, null);
       }
     }

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/602a1e78/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
index 506b682..55cd6ba 100644
--- a/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
+++ b/zeppelin-zengine/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
@@ -320,18 +320,59 @@ public class NotebookTest implements JobListenerFactory{
         .getInterpreterSettings().get(0).getInterpreterGroup()
         .getAngularObjectRegistry();
 
-    // add local scope object
-    registry.add("o1", "object1", note.id(), null);
+    Paragraph p1 = note.addParagraph();
+
+    // add paragraph scope object
+    registry.add("o1", "object1", note.id(), p1.getId());
+
+    // add notebook scope object
+    registry.add("o2", "object2", note.id(), null);
+
     // add global scope object
-    registry.add("o2", "object2", null, null);
+    registry.add("o3", "object3", null, null);
 
     // remove notebook
     notebook.removeNote(note.id());
 
-    // local object should be removed
+    // notebook scope or paragraph scope object should be removed
     assertNull(registry.get("o1", note.id(), null));
+    assertNull(registry.get("o2", note.id(), p1.getId()));
+
     // global object sould be remained
-    assertNotNull(registry.get("o2", null, null));
+    assertNotNull(registry.get("o3", null, null));
+  }
+
+  @Test
+  public void testAngularObjectRemovalOnParagraphRemove() throws InterruptedException,
+      IOException {
+    // create a note and a paragraph
+    Note note = notebook.createNote();
+    note.getNoteReplLoader().setInterpreters(factory.getDefaultInterpreterSettingList());
+
+    AngularObjectRegistry registry = note.getNoteReplLoader()
+        .getInterpreterSettings().get(0).getInterpreterGroup()
+        .getAngularObjectRegistry();
+
+    Paragraph p1 = note.addParagraph();
+
+    // add paragraph scope object
+    registry.add("o1", "object1", note.id(), p1.getId());
+
+    // add notebook scope object
+    registry.add("o2", "object2", note.id(), null);
+
+    // add global scope object
+    registry.add("o3", "object3", null, null);
+
+    // remove notebook
+    note.removeParagraph(p1.getId());
+
+    // paragraph scope should be removed
+    assertNull(registry.get("o1", note.id(), null));
+
+    // notebook scope and global object sould be remained
+    assertNotNull(registry.get("o2", note.id(), null));
+    assertNotNull(registry.get("o3", null, null));
   }
 
   @Test