diff --git a/package-lock.json b/package-lock.json
index 4ee26757f7..0f8dabb348 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "wise",
-  "version": "5.20.4",
+  "version": "5.20.5",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 94f8210b6b..71c70cf53c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "wise",
-  "version": "5.20.4",
+  "version": "5.20.5",
   "description": "Web-based Inquiry Science Environment",
   "main": "app.js",
   "browserslist": [
diff --git a/pom.xml b/pom.xml
index b12ac1d1d8..d7c44772ce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,7 +34,7 @@
   <artifactId>wise</artifactId>
   <packaging>war</packaging>
   <name>Web-based Inquiry Science Environment</name>
-  <version>5.20.4</version>
+  <version>5.20.5</version>
   <url>http://wise5.org</url>
   <licenses>
     <license>
diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java
index 79a5d8a107..72da0d69c3 100644
--- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java
+++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java
@@ -14,7 +14,6 @@
 import org.apache.commons.lang3.RandomStringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.MessageSource;
 import org.springframework.security.access.annotation.Secured;
 import org.springframework.security.acls.model.Permission;
 import org.springframework.security.core.Authentication;
@@ -38,7 +37,6 @@
 import org.wise.portal.presentation.web.response.SimpleResponse;
 import org.wise.portal.service.authentication.DuplicateUsernameException;
 import org.wise.portal.service.authentication.UserDetailsService;
-import org.wise.portal.service.mail.IMailFacade;
 
 /**
  * Teacher REST API
@@ -55,12 +53,6 @@ public class TeacherAPIController extends UserAPIController {
   @Autowired
   private UserDetailsService userDetailsService;
 
-  @Autowired
-  protected IMailFacade mailService;
-
-  @Autowired
-  protected MessageSource messageSource;
-
   @Value("${google.clientId:}")
   private String googleClientId;
 
diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIController.java
new file mode 100644
index 0000000000..d2cba48c11
--- /dev/null
+++ b/src/main/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIController.java
@@ -0,0 +1,84 @@
+package org.wise.portal.presentation.web.controllers.user;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+import javax.mail.MessagingException;
+
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.wise.portal.domain.authentication.impl.PersistentUserDetails;
+import org.wise.portal.domain.authentication.impl.TeacherUserDetails;
+import org.wise.portal.domain.user.User;
+import org.wise.portal.presentation.web.exception.InvalidPasswordExcpetion;
+
+@RestController
+@RequestMapping("/api/google-user")
+public class GoogleUserAPIController extends UserAPIController {
+
+  @GetMapping("/check-user-exists")
+  boolean isGoogleIdExist(@RequestParam String googleUserId) {
+    return userService.retrieveUserByGoogleUserId(googleUserId) != null;
+  }
+
+  @GetMapping("/check-user-matches")
+  boolean isGoogleIdMatches(@RequestParam String googleUserId, @RequestParam String userId) {
+    User user = userService.retrieveUserByGoogleUserId(googleUserId);
+    return user != null && user.getId().toString().equals(userId);
+  }
+
+  @GetMapping("/get-user")
+  HashMap<String, Object> getUserByGoogleId(@RequestParam String googleUserId) {
+    User user = userService.retrieveUserByGoogleUserId(googleUserId);
+    HashMap<String, Object> response = new HashMap<String, Object>();
+    if (user == null) {
+      response.put("status", "error");
+    } else {
+      response.put("status", "success");
+      response.put("userId", user.getId());
+      response.put("username", user.getUserDetails().getUsername());
+      response.put("firstName", user.getUserDetails().getFirstname());
+      response.put("lastName", user.getUserDetails().getLastname());
+    }
+    return response;
+  }
+
+  @Secured("ROLE_USER")
+  @PostMapping("/unlink-account")
+  HashMap<String, Object> unlinkGoogleAccount(Authentication auth, @RequestParam String newPassword)
+      throws InvalidPasswordExcpetion {
+    if (newPassword.isEmpty()) {
+      throw new InvalidPasswordExcpetion();
+    }
+    String username = auth.getName();
+    User user = userService.retrieveUserByUsername(username);
+    ((PersistentUserDetails) user.getUserDetails()).setGoogleUserId(null);
+    userService.updateUserPassword(user, newPassword);
+    boolean isSendEmail = Boolean.parseBoolean(appProperties.getProperty("send_email_enabled", "false"));
+    if (isSendEmail && user.isTeacher()) {
+      this.sendUnlinkGoogleEmail((TeacherUserDetails) user.getUserDetails());
+    }
+    return this.getUserInfo(auth, username);
+  }
+
+  private void sendUnlinkGoogleEmail(TeacherUserDetails userDetails) {
+    String[] recipients = { userDetails.getEmailAddress() };
+    String subject = messageSource.getMessage("unlink_google_account_success_email_subject", null,
+      "Successfully Unlinked Google Account", new Locale(userDetails.getLanguage()));
+    String username = userDetails.getUsername();
+    String message = messageSource.getMessage("unlink_google_account_success_email_body",
+      new Object[]{username},
+      "You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created. Your username is: " + username,
+      new Locale(userDetails.getLanguage()));
+    try {
+      mailService.postMail(recipients, subject, message, appProperties.getProperty("portalemailaddress"));
+    } catch (MessagingException e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java
index b6ef0251f3..21673c81d5 100644
--- a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java
+++ b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java
@@ -12,6 +12,7 @@
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.MessageSource;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
@@ -63,6 +64,9 @@ public class UserAPIController {
   @Autowired
   protected IMailFacade mailService;
 
+  @Autowired
+  protected MessageSource messageSource;
+
   @Value("${google.clientId:}")
   protected String googleClientId = "";
 
@@ -189,33 +193,6 @@ List<HashMap<String, String>> getSupportedLanguages() {
     return langs;
   }
 
-  @GetMapping("/check-google-user-exists")
-  boolean isGoogleIdExist(@RequestParam String googleUserId) {
-    return userService.retrieveUserByGoogleUserId(googleUserId) != null;
-  }
-
-  @GetMapping("/check-google-user-matches")
-  boolean isGoogleIdMatches(@RequestParam String googleUserId, @RequestParam String userId) {
-    User user = userService.retrieveUserByGoogleUserId(googleUserId);
-    return user != null && user.getId().toString().equals(userId);
-  }
-
-  @GetMapping("/google-user")
-  HashMap<String, Object> getUserByGoogleId(@RequestParam String googleUserId) {
-    User user = userService.retrieveUserByGoogleUserId(googleUserId);
-    HashMap<String, Object> response = new HashMap<String, Object>();
-    if (user == null) {
-      response.put("status", "error");
-    } else {
-      response.put("status", "success");
-      response.put("userId", user.getId());
-      response.put("username", user.getUserDetails().getUsername());
-      response.put("firstName", user.getUserDetails().getFirstname());
-      response.put("lastName", user.getUserDetails().getLastname());
-    }
-    return response;
-  }
-
   private String getLanguageName(String localeString) {
     if (localeString.toLowerCase().equals("zh_tw")) {
       return "Chinese (Traditional)";
diff --git a/src/main/java/org/wise/portal/presentation/web/exception/InvalidPasswordExcpetion.java b/src/main/java/org/wise/portal/presentation/web/exception/InvalidPasswordExcpetion.java
new file mode 100644
index 0000000000..6d6dac6cbc
--- /dev/null
+++ b/src/main/java/org/wise/portal/presentation/web/exception/InvalidPasswordExcpetion.java
@@ -0,0 +1,6 @@
+package org.wise.portal.presentation.web.exception;
+
+public class InvalidPasswordExcpetion extends Exception {
+
+  private static final long serialVersionUID = 1L;
+}
diff --git a/src/main/resources/i18n/i18n.properties b/src/main/resources/i18n/i18n.properties
index 7c050e8dcb..1ffd28e71c 100644
--- a/src/main/resources/i18n/i18n.properties
+++ b/src/main/resources/i18n/i18n.properties
@@ -308,6 +308,12 @@ teacher_cap.description=Text for the word "Teacher"
 team_cap=Team
 team_cap.description=Text for the word "Team"
 
+unlink_google_account_success_email_subject=Successfully Unlinked Google Account
+unlink_google_account_success_email_subject.description=Subject text in email to notify user about successfuly unlinking google account
+
+unlink_google_account_success_email_body=You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created.\n\nYour username is: {0}\n\nThank you for using WISE,\nWISE Team
+unlink_google_account_success_email_body.description=Body text in email to notify user about successfully unlinking google account
+
 # Root (/) Pages #
 
 accountmenu.forgot=Forgot Username or Password?
diff --git a/src/main/resources/version.txt b/src/main/resources/version.txt
index c523b9689b..fa20f9833e 100644
--- a/src/main/resources/version.txt
+++ b/src/main/resources/version.txt
@@ -1 +1 @@
-5.20.4
+5.20.5
diff --git a/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts b/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts
index 8a2db347d5..5347591222 100644
--- a/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts
+++ b/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts
@@ -5,7 +5,6 @@ import { finalize } from 'rxjs/operators';
 import { LibraryProject } from '../libraryProject';
 import { LibraryService } from '../../../services/library.service';
 import { MatSnackBar } from '@angular/material/snack-bar';
-import { Subscription } from 'rxjs';
 
 @Component({
   selector: 'app-copy-project-dialog',
diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html
index 1a47a25674..e2fd57a2df 100644
--- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html
+++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html
@@ -57,5 +57,17 @@
   </div>
 </form>
 <ng-container *ngIf="isGoogleUser">
-  <p class="notice" i18n>This account was created using Google and doesn't use a WISE password. If you would like to unlink your Google account, please <a routerLink="/contact">contact us</a>.</p>
+  <p fxLayoutAlign="start center" fxLayoutGap="8px" i18n>
+    <img class="google-icon" src="assets/img/icons/g-logo.png" i18n-alt alt="Google logo" />
+    <span>This account was created using Google and doesn't use a WISE password.</span>
+  </p>
+  <p>
+    <button id="unlinkGoogleAccount"
+        class="unlink"
+        type="button"
+        mat-raised-button
+        (click)="unlinkGoogleAccount()">
+      <span class="warn" i18n>Unlink Google Account</span>
+    </button>
+  </p>
 </ng-container>
diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss
index 940482c43e..60fab472cc 100644
--- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss
+++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss
@@ -11,6 +11,11 @@ form {
   }
 }
 
-.notice {
-  margin: 0 auto;
+.google-icon {
+  height: 1.8em;
+  width: auto;
+}
+
+.unlink {
+  margin: 8px 0;
 }
diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts
index 6d33a734a5..fd68f182c1 100644
--- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts
+++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts
@@ -1,14 +1,17 @@
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 import { EditPasswordComponent } from './edit-password.component';
 import { UserService } from '../../../services/user.service';
-import { BehaviorSubject, Observable } from 'rxjs';
+import { BehaviorSubject, Observable, of } from 'rxjs';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { ReactiveFormsModule } from '@angular/forms';
-import { NO_ERRORS_SCHEMA, Provider } from '@angular/core';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
 import { MatSnackBarModule } from '@angular/material/snack-bar';
 import { By } from '@angular/platform-browser';
 import { User } from '../../../domain/user';
 import { configureTestSuite } from 'ng-bullet';
+import { MatDialogModule } from '@angular/material/dialog';
+const CORRECT_OLD_PASS = 'a';
+const INCORRECT_OLD_PASS = 'b';
 
 export class MockUserService {
   getUser(): BehaviorSubject<User> {
@@ -22,13 +25,13 @@ export class MockUserService {
   }
 
   changePassword(username, oldPassword, newPassword) {
-    if (oldPassword === 'a') {
-      return Observable.create((observer) => {
+    if (oldPassword === CORRECT_OLD_PASS) {
+      return new Observable((observer) => {
         observer.next({ status: 'success', messageCode: 'passwordChanged' });
         observer.complete();
       });
     } else {
-      return Observable.create((observer) => {
+      return new Observable((observer) => {
         observer.next({ status: 'error', messageCode: 'incorrectPassword' });
         observer.complete();
       });
@@ -36,22 +39,26 @@ export class MockUserService {
   }
 }
 
-describe('EditPasswordComponent', () => {
-  let component: EditPasswordComponent;
-  let fixture: ComponentFixture<EditPasswordComponent>;
+let component: EditPasswordComponent;
+let fixture: ComponentFixture<EditPasswordComponent>;
+
+const getSubmitButton = () => {
+  return fixture.debugElement.nativeElement.querySelector('button[type="submit"]');
+};
 
-  const getSubmitButton = () => {
-    return fixture.debugElement.nativeElement.querySelector('button[type="submit"]');
-  };
+const getUnlinkGoogleAccountButton = () => {
+  return fixture.debugElement.nativeElement.querySelector('button[id="unlinkGoogleAccount"]');
+};
 
-  const getForm = () => {
-    return fixture.debugElement.query(By.css('form'));
-  };
+const getForm = () => {
+  return fixture.debugElement.query(By.css('form'));
+};
 
+describe('EditPasswordComponent', () => {
   configureTestSuite(() => {
     TestBed.configureTestingModule({
       declarations: [EditPasswordComponent],
-      imports: [BrowserAnimationsModule, ReactiveFormsModule, MatSnackBarModule],
+      imports: [BrowserAnimationsModule, ReactiveFormsModule, MatSnackBarModule, MatDialogModule],
       providers: [{ provide: UserService, useValue: new MockUserService() }],
       schemas: [NO_ERRORS_SCHEMA]
     });
@@ -63,61 +70,60 @@ describe('EditPasswordComponent', () => {
     fixture.detectChanges();
   });
 
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
+  initialState_disableSubmitButton();
+  validForm_enableSubmitButton();
+  passwordMismatch_disableSubmitButtonAndInvalidateForm();
+  oldPasswordIncorrect_disableSubmitButtonAndShowError();
+  formSubmit_disableSubmitButton();
+  passwordChanged_handleResponse();
+  incorrectPassword_showError();
+  notGoogleUser_showUnlinkOption();
+  unlinkGoogleButtonClick_showDialog();
+});
 
+function initialState_disableSubmitButton() {
   it('should disable submit button and invalidate form on initial state', () => {
     expect(component.changePasswordFormGroup.valid).toBeFalsy();
-    const submitButton = getSubmitButton();
-    expect(submitButton.disabled).toBe(true);
+    expectSubmitButtonDisabled();
   });
+}
 
+function validForm_enableSubmitButton() {
   it('should enable submit button when form is valid', () => {
-    component.changePasswordFormGroup.get('oldPassword').setValue('a');
-    component.newPasswordFormGroup.get('newPassword').setValue('b');
-    component.newPasswordFormGroup.get('confirmNewPassword').setValue('b');
-    fixture.detectChanges();
-    const submitButton = getSubmitButton();
-    expect(submitButton.disabled).toBe(false);
+    setPasswords(CORRECT_OLD_PASS, 'b', 'b');
+    expectSubmitButtonEnabled();
     expect(component.changePasswordFormGroup.valid).toBeTruthy();
   });
+}
 
+function passwordMismatch_disableSubmitButtonAndInvalidateForm() {
   it('should disable submit button and invalidate form when new password and confirm new password fields do not match', () => {
-    component.changePasswordFormGroup.get('oldPassword').setValue('a');
-    component.newPasswordFormGroup.get('newPassword').setValue('a');
-    component.newPasswordFormGroup.get('confirmNewPassword').setValue('b');
-    fixture.detectChanges();
-    const submitButton = getSubmitButton();
-    expect(submitButton.disabled).toBe(true);
+    setPasswords(CORRECT_OLD_PASS, 'a', 'b');
+    expectSubmitButtonDisabled();
     expect(component.changePasswordFormGroup.valid).toBeFalsy();
   });
+}
 
+function oldPasswordIncorrect_disableSubmitButtonAndShowError() {
   it('should disable submit button and set incorrectPassword error when old password is incorrect', async () => {
-    component.changePasswordFormGroup.get('oldPassword').setValue('b');
-    component.newPasswordFormGroup.get('newPassword').setValue('c');
-    component.newPasswordFormGroup.get('confirmNewPassword').setValue('c');
-    const form = getForm();
-    form.triggerEventHandler('submit', null);
-    fixture.detectChanges();
-    const submitButton = getSubmitButton();
-    expect(submitButton.disabled).toBe(true);
+    setPasswords(INCORRECT_OLD_PASS, 'c', 'c');
+    submitForm();
+    expectSubmitButtonDisabled();
     expect(component.changePasswordFormGroup.get('oldPassword').getError('incorrectPassword')).toBe(
       true
     );
   });
+}
 
+function formSubmit_disableSubmitButton() {
   it('should disable submit button when form is successfully submitted', async () => {
-    component.changePasswordFormGroup.get('oldPassword').setValue('a');
-    component.newPasswordFormGroup.get('newPassword').setValue('b');
-    component.newPasswordFormGroup.get('confirmNewPassword').setValue('b');
-    const form = getForm();
-    form.triggerEventHandler('submit', null);
-    fixture.detectChanges();
-    const submitButton = getSubmitButton();
-    expect(submitButton.disabled).toBe(true);
+    setPasswords(CORRECT_OLD_PASS, 'b', 'b');
+    submitForm();
+    expectSubmitButtonDisabled();
   });
+}
 
+function passwordChanged_handleResponse() {
   it('should handle the change password response when the password was successfully changed', () => {
     const resetFormSpy = spyOn(component, 'resetForm');
     const snackBarSpy = spyOn(component.snackBar, 'open');
@@ -129,7 +135,9 @@ describe('EditPasswordComponent', () => {
     expect(resetFormSpy).toHaveBeenCalled();
     expect(snackBarSpy).toHaveBeenCalled();
   });
+}
 
+function incorrectPassword_showError() {
   it('should handle the change password response when the password was incorrect', () => {
     const response = {
       status: 'error',
@@ -140,4 +148,44 @@ describe('EditPasswordComponent', () => {
       true
     );
   });
-});
+}
+
+function notGoogleUser_showUnlinkOption() {
+  it('should hide show option to unlink google account if the user is not a google user', () => {
+    expect(getUnlinkGoogleAccountButton()).toBeNull();
+  });
+}
+
+function unlinkGoogleButtonClick_showDialog() {
+  it('clicking on unlink google account link should open a dialog', () => {
+    const dialogSpy = spyOn(component.dialog, 'open');
+    setGoogleUser();
+    getUnlinkGoogleAccountButton().click();
+    expect(dialogSpy).toHaveBeenCalled();
+  });
+}
+
+export function expectSubmitButtonDisabled() {
+  expect(getSubmitButton().disabled).toBe(true);
+}
+
+function expectSubmitButtonEnabled() {
+  expect(getSubmitButton().disabled).toBe(false);
+}
+
+function submitForm() {
+  getForm().triggerEventHandler('submit', null);
+  fixture.detectChanges();
+}
+
+function setGoogleUser() {
+  component.isGoogleUser = true;
+  fixture.detectChanges();
+}
+
+function setPasswords(oldPass: string, newPass: string, newPassConfirm: string) {
+  component.changePasswordFormGroup.get('oldPassword').setValue(oldPass);
+  component.newPasswordFormGroup.get('newPassword').setValue(newPass);
+  component.newPasswordFormGroup.get('confirmNewPassword').setValue(newPassConfirm);
+  fixture.detectChanges();
+}
diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts
index f22788cc5f..7539bc803e 100644
--- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts
+++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts
@@ -1,15 +1,18 @@
-import { Component, OnInit, ViewChild } from '@angular/core';
+import { Component, ViewChild } from '@angular/core';
 import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
 import { finalize } from 'rxjs/operators';
+import { MatDialog } from '@angular/material/dialog';
 import { MatSnackBar } from '@angular/material/snack-bar';
 import { UserService } from '../../../services/user.service';
+import { UnlinkGoogleAccountConfirmComponent } from '../unlink-google-account-confirm/unlink-google-account-confirm.component';
+import { passwordMatchValidator } from '../validators/password-match.validator';
 
 @Component({
   selector: 'app-edit-password',
   templateUrl: './edit-password.component.html',
   styleUrls: ['./edit-password.component.scss']
 })
-export class EditPasswordComponent implements OnInit {
+export class EditPasswordComponent {
   @ViewChild('changePasswordForm', { static: false }) changePasswordForm;
   isSaving: boolean = false;
   isGoogleUser: boolean = false;
@@ -19,7 +22,7 @@ export class EditPasswordComponent implements OnInit {
       newPassword: new FormControl('', [Validators.required]),
       confirmNewPassword: new FormControl('', [Validators.required])
     },
-    { validator: this.passwordMatchValidator }
+    { validator: passwordMatchValidator }
   );
 
   changePasswordFormGroup: FormGroup = this.fb.group({
@@ -30,6 +33,7 @@ export class EditPasswordComponent implements OnInit {
   constructor(
     private fb: FormBuilder,
     private userService: UserService,
+    public dialog: MatDialog,
     public snackBar: MatSnackBar
   ) {}
 
@@ -39,18 +43,6 @@ export class EditPasswordComponent implements OnInit {
     });
   }
 
-  passwordMatchValidator(passwordsFormGroup: FormGroup) {
-    const newPassword = passwordsFormGroup.get('newPassword').value;
-    const confirmNewPassword = passwordsFormGroup.get('confirmNewPassword').value;
-    if (newPassword === confirmNewPassword) {
-      return null;
-    } else {
-      const error = { passwordDoesNotMatch: true };
-      passwordsFormGroup.controls['confirmNewPassword'].setErrors(error);
-      return error;
-    }
-  }
-
   saveChanges() {
     this.isSaving = true;
     const oldPassword: string = this.getControlFieldValue('oldPassword');
@@ -90,6 +82,12 @@ export class EditPasswordComponent implements OnInit {
     }
   }
 
+  unlinkGoogleAccount() {
+    this.dialog.open(UnlinkGoogleAccountConfirmComponent, {
+      panelClass: 'mat-dialog--sm'
+    });
+  }
+
   resetForm() {
     this.changePasswordForm.resetForm();
   }
diff --git a/src/main/webapp/site/src/app/modules/shared/shared.module.ts b/src/main/webapp/site/src/app/modules/shared/shared.module.ts
index 5ec6a150df..807f04e4e7 100644
--- a/src/main/webapp/site/src/app/modules/shared/shared.module.ts
+++ b/src/main/webapp/site/src/app/modules/shared/shared.module.ts
@@ -5,6 +5,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 import { MatButtonModule } from '@angular/material/button';
 import { MatCardModule } from '@angular/material/card';
+import { MatDialogModule } from '@angular/material/dialog';
 import { MatFormFieldModule } from '@angular/material/form-field';
 import { MatIconModule } from '@angular/material/icon';
 import { MatInputModule } from '@angular/material/input';
@@ -13,6 +14,7 @@ import { MatSelectModule } from '@angular/material/select';
 const materialModules = [
   MatButtonModule,
   MatCardModule,
+  MatDialogModule,
   MatIconModule,
   MatInputModule,
   MatFormFieldModule,
@@ -26,6 +28,9 @@ import { HeroSectionComponent } from './hero-section/hero-section.component';
 import { SearchBarComponent } from './search-bar/search-bar.component';
 import { SelectMenuComponent } from './select-menu/select-menu.component';
 import { EditPasswordComponent } from './edit-password/edit-password.component';
+import { UnlinkGoogleAccountConfirmComponent } from './unlink-google-account-confirm/unlink-google-account-confirm.component';
+import { UnlinkGoogleAccountPasswordComponent } from './unlink-google-account-password/unlink-google-account-password.component';
+import { UnlinkGoogleAccountSuccessComponent } from './unlink-google-account-success/unlink-google-account-success.component';
 
 @NgModule({
   imports: [
@@ -52,7 +57,10 @@ import { EditPasswordComponent } from './edit-password/edit-password.component';
     HeroSectionComponent,
     SearchBarComponent,
     SelectMenuComponent,
-    EditPasswordComponent
+    EditPasswordComponent,
+    UnlinkGoogleAccountConfirmComponent,
+    UnlinkGoogleAccountPasswordComponent,
+    UnlinkGoogleAccountSuccessComponent
   ]
 })
 export class SharedModule {}
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html
new file mode 100644
index 0000000000..9fd9a47c13
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html
@@ -0,0 +1,16 @@
+<h2 class="mat-dialog-title" fxLayoutAlign="start center" fxLayoutGap="8px">
+  <img class="google-icon" src="assets/img/icons/g-logo.png" i18n-alt alt="Google logo" />
+  <span i18n>Unlink Google Account</span>
+  <span fxFlex></span>
+  <mat-icon color="warn" i18n-aria-label aria-label="Warning">warning</mat-icon>
+</h2>
+<mat-dialog-content>
+  <div class="info-block">
+    <p i18n>To remove the link to your Google account, you will be asked to create a WISE password. In the future, you'll sign in to WISE using your username and password.</p>
+    <p><strong i18n>You will no longer be able to sign in to WISE using Google. Would you like to continue?</strong></p>
+  </div>
+</mat-dialog-content>
+<mat-dialog-actions fxLayout="row" fxLayoutAlign="end">
+  <button mat-flat-button color="primary" mat-dialog-close i18n>Cancel</button>
+  <a mat-button color="warn" (click)="continue()" i18n>Continue</a>
+</mat-dialog-actions>
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss
new file mode 100644
index 0000000000..257ab6ea45
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss
@@ -0,0 +1,4 @@
+.google-icon {
+  height: 1.4em;
+  width: auto;
+}
\ No newline at end of file
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.spec.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.spec.ts
new file mode 100644
index 0000000000..400a72a901
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.spec.ts
@@ -0,0 +1,37 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MatDialogModule } from '@angular/material/dialog';
+import { configureTestSuite } from 'ng-bullet';
+import { UnlinkGoogleAccountConfirmComponent } from './unlink-google-account-confirm.component';
+
+let component: UnlinkGoogleAccountConfirmComponent;
+let fixture: ComponentFixture<UnlinkGoogleAccountConfirmComponent>;
+
+describe('UnlinkGoogleAccountConfirmComponent', () => {
+  configureTestSuite(() => {
+    TestBed.configureTestingModule({
+      declarations: [UnlinkGoogleAccountConfirmComponent],
+      imports: [MatDialogModule],
+      providers: [],
+      schemas: [NO_ERRORS_SCHEMA]
+    });
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(UnlinkGoogleAccountConfirmComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  continue_closeAllDialogsAndOpenChangePasswordDialog();
+});
+
+function continue_closeAllDialogsAndOpenChangePasswordDialog() {
+  it('continue() should closeAllDialogs and open a new dialog to edit password', () => {
+    const closeAllDialogSpy = spyOn(component.dialog, 'closeAll');
+    const openDialogSpy = spyOn(component.dialog, 'open');
+    component.continue();
+    expect(closeAllDialogSpy).toHaveBeenCalled();
+    expect(openDialogSpy).toHaveBeenCalled();
+  });
+}
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts
new file mode 100644
index 0000000000..99a4fe2fd8
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { UnlinkGoogleAccountPasswordComponent } from '../unlink-google-account-password/unlink-google-account-password.component';
+
+@Component({
+  styleUrls: ['./unlink-google-account-confirm.component.scss'],
+  templateUrl: './unlink-google-account-confirm.component.html'
+})
+export class UnlinkGoogleAccountConfirmComponent {
+  constructor(public dialog: MatDialog) {}
+
+  continue() {
+    this.dialog.closeAll();
+    this.dialog.open(UnlinkGoogleAccountPasswordComponent, {
+      panelClass: 'mat-dialog--sm'
+    });
+  }
+}
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html
new file mode 100644
index 0000000000..e5c496120f
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html
@@ -0,0 +1,41 @@
+<h2 class="mat-dialog-title" fxLayoutAlign="start center" fxLayoutGap="8px">
+  <img class="google-icon" src="assets/img/icons/g-logo.png" i18n-alt alt="Google logo" />
+  <span i18n>Unlink Google Account</span>
+</h2>
+<form [formGroup]="newPasswordFormGroup">
+  <mat-dialog-content class="mat-dialog-content--scroll" fxLayout="column">
+    <h3 i18n>Create a WISE password:</h3>
+    <mat-form-field appearance="fill">
+      <mat-label i18n>New Password</mat-label>
+      <input matInput
+          id="newPassword"
+          type="password"
+          name="newPassword"
+          formControlName="newPassword"
+          required />
+      <mat-error *ngIf="newPasswordFormGroup.controls['newPassword'].hasError('required')" i18n>New Password required</mat-error>
+    </mat-form-field>
+    <mat-form-field appearance="fill">
+      <mat-label i18n>Confirm New Password</mat-label>
+      <input matInput
+          id="confirmNewPassword"
+          type="password"
+          name="confirmNewPassword"
+          formControlName="confirmNewPassword"
+          required />
+      <mat-error *ngIf="newPasswordFormGroup.controls['confirmNewPassword'].hasError('required')" i18n>Confirm Password required</mat-error>
+      <mat-error *ngIf="newPasswordFormGroup.hasError('passwordDoesNotMatch')" i18n>Passwords do not match</mat-error>
+    </mat-form-field>
+  </mat-dialog-content>
+  <mat-dialog-actions fxLayoutAlign="end">
+    <button mat-button mat-dialog-close i18n>Cancel</button>
+    <button mat-raised-button
+        color="primary"
+        type="submit"
+        [disabled]="!newPasswordFormGroup.valid || isSaving"
+        (click)="submit()">
+      <ng-container i18n>Submit</ng-container>
+      <mat-progress-bar mode="indeterminate" *ngIf="isSaving"></mat-progress-bar>
+    </button>
+  </mat-dialog-actions>
+</form>
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss
new file mode 100644
index 0000000000..6629b7fe81
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss
@@ -0,0 +1,4 @@
+.google-icon {
+  height: 1.4em;
+  width: auto;
+}
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts
new file mode 100644
index 0000000000..d10a68730e
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts
@@ -0,0 +1,51 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { MatDialogModule } from '@angular/material/dialog';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { configureTestSuite } from 'ng-bullet';
+import { Subscription } from 'rxjs';
+import { UserService } from '../../../services/user.service';
+import { UnlinkGoogleAccountPasswordComponent } from './unlink-google-account-password.component';
+
+class MockUserService {
+  unlinkGoogleUser(newPassword: string) {
+    return new Subscription();
+  }
+}
+
+let component: UnlinkGoogleAccountPasswordComponent;
+let fixture: ComponentFixture<UnlinkGoogleAccountPasswordComponent>;
+let userService = new MockUserService();
+
+describe('UnlinkGoogleAccountPasswordComponent', () => {
+  configureTestSuite(() => {
+    TestBed.configureTestingModule({
+      declarations: [UnlinkGoogleAccountPasswordComponent],
+      imports: [BrowserAnimationsModule, ReactiveFormsModule, MatDialogModule],
+      providers: [{ provide: UserService, useValue: userService }],
+      schemas: [NO_ERRORS_SCHEMA]
+    });
+  });
+  beforeEach(() => {
+    fixture = TestBed.createComponent(UnlinkGoogleAccountPasswordComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+  formSubmit_callUserServiceUnlinkGoogleUserFunction();
+});
+
+function formSubmit_callUserServiceUnlinkGoogleUserFunction() {
+  it('should call UserService.UnlinkGoogleUserFunction when form is submitted', () => {
+    const unlinkFunctionSpy = spyOn(userService, 'unlinkGoogleUser').and.returnValue(
+      new Subscription()
+    );
+    const newPassword = 'aloha';
+    component.newPasswordFormGroup.setValue({
+      newPassword: newPassword,
+      confirmNewPassword: newPassword
+    });
+    component.submit();
+    expect(unlinkFunctionSpy).toHaveBeenCalledWith(newPassword);
+  });
+}
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts
new file mode 100644
index 0000000000..da0a00f3a0
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts
@@ -0,0 +1,40 @@
+import { Component } from '@angular/core';
+import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
+import { MatDialog } from '@angular/material/dialog';
+import { UserService } from '../../../services/user.service';
+import { UnlinkGoogleAccountSuccessComponent } from '../unlink-google-account-success/unlink-google-account-success.component';
+import { passwordMatchValidator } from '../validators/password-match.validator';
+
+@Component({
+  styleUrls: ['./unlink-google-account-password.component.scss'],
+  templateUrl: './unlink-google-account-password.component.html'
+})
+export class UnlinkGoogleAccountPasswordComponent {
+  isSaving: boolean = false;
+  newPasswordFormGroup: FormGroup = this.fb.group(
+    {
+      newPassword: new FormControl('', [Validators.required]),
+      confirmNewPassword: new FormControl('', [Validators.required])
+    },
+    { validator: passwordMatchValidator }
+  );
+
+  constructor(
+    private fb: FormBuilder,
+    public dialog: MatDialog,
+    private userService: UserService
+  ) {}
+
+  submit() {
+    this.isSaving = true;
+    this.userService
+      .unlinkGoogleUser(this.newPasswordFormGroup.get('newPassword').value)
+      .add(() => {
+        this.isSaving = false;
+        this.dialog.closeAll();
+        this.dialog.open(UnlinkGoogleAccountSuccessComponent, {
+          panelClass: 'mat-dialog--sm'
+        });
+      });
+  }
+}
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html
new file mode 100644
index 0000000000..c9d91706f6
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html
@@ -0,0 +1,13 @@
+<h2 class="mat-dialog-title" fxLayoutAlign="start center" fxLayoutGap="8px">
+  <img class="google-icon" src="assets/img/icons/g-logo.png" i18n-alt alt="Google logo" />
+  <span i18n>Unlink Google Account</span>
+</h2>
+<mat-dialog-content>
+  <div class="info-block">
+    <p i18n>Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created.</p>
+    <p i18n>Your username is: <span class="mat-body-2">{{ username }}</span>.</p>
+  </div>
+</mat-dialog-content>
+<mat-dialog-actions fxLayoutAlign="end">
+  <button mat-button mat-dialog-close i18n>Done</button>
+</mat-dialog-actions>
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss
new file mode 100644
index 0000000000..6629b7fe81
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss
@@ -0,0 +1,4 @@
+.google-icon {
+  height: 1.4em;
+  width: auto;
+}
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.ts
new file mode 100644
index 0000000000..1701865273
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { Teacher } from '../../../domain/teacher';
+import { UserService } from '../../../services/user.service';
+
+@Component({
+  styleUrls: ['unlink-google-account-success.component.scss'],
+  templateUrl: 'unlink-google-account-success.component.html'
+})
+export class UnlinkGoogleAccountSuccessComponent {
+  username: string;
+
+  constructor(private userService: UserService) {}
+
+  ngOnInit() {
+    const user = <Teacher>this.userService.getUser().getValue();
+    this.username = user.username;
+  }
+}
diff --git a/src/main/webapp/site/src/app/modules/shared/validators/password-match.validator.ts b/src/main/webapp/site/src/app/modules/shared/validators/password-match.validator.ts
new file mode 100644
index 0000000000..89ac014720
--- /dev/null
+++ b/src/main/webapp/site/src/app/modules/shared/validators/password-match.validator.ts
@@ -0,0 +1,13 @@
+import { FormGroup } from '@angular/forms';
+
+export function passwordMatchValidator(passwordsFormGroup: FormGroup) {
+  const newPassword = passwordsFormGroup.get('newPassword').value;
+  const confirmNewPassword = passwordsFormGroup.get('confirmNewPassword').value;
+  if (newPassword === confirmNewPassword) {
+    return null;
+  } else {
+    const error = { passwordDoesNotMatch: true };
+    passwordsFormGroup.controls['confirmNewPassword'].setErrors(error);
+    return error;
+  }
+}
diff --git a/src/main/webapp/site/src/app/services/milestoneService.spec.ts b/src/main/webapp/site/src/app/services/milestoneService.spec.ts
index a9d7ec91b9..69177122c3 100644
--- a/src/main/webapp/site/src/app/services/milestoneService.spec.ts
+++ b/src/main/webapp/site/src/app/services/milestoneService.spec.ts
@@ -48,6 +48,10 @@ const aggregateAutoScoresSample = {
 
 const possibleScoresKi = [1, 2, 3, 4, 5];
 
+const sampleAggregateData = {
+  counts: createScoreCounts([10, 20, 30, 40, 50])
+};
+
 const reportSettingsCustomScoreValuesSample = {
   customScoreValues: {
     ki: [1, 2, 3, 4]
@@ -964,205 +968,39 @@ function isPercentOfScoresNotEqualTo() {
 
 function getComparatorSum() {
   describe('getComparatorSum()', () => {
-    getGreaterThanSum();
-    getGreaterThanOrEqualToSum();
-    getLessThanSum();
-    getEqualToSum();
-    getNotEqualToSum();
-  });
-}
-
-function getGreaterThanSum() {
-  const aggregateData = {
-    counts: createScoreCounts([10, 20, 30, 40, 50])
-  };
-  it('should get greater than sum with score 1', () => {
-    const satisfyCriterion = { value: 1 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.greaterThan
-      )
-    ).toEqual(140);
-  });
-  it('should get greater than sum with score 2', () => {
-    const satisfyCriterion = { value: 2 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.greaterThan
-      )
-    ).toEqual(120);
-  });
-  it('should get greater than sum with score 3', () => {
-    const satisfyCriterion = { value: 3 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.greaterThan
-      )
-    ).toEqual(90);
-  });
-  it('should get greater than sum with score 4', () => {
-    const satisfyCriterion = { value: 4 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.greaterThan
-      )
-    ).toEqual(50);
+    getComparatorSum_greaterThan0_ReturnSumAll();
+    getComparatorSum_greaterThan3_ReturnSumPartial();
+    getComparatorSum_greaterThan5_Return0();
   });
 }
 
-function getGreaterThanOrEqualToSum() {
-  const aggregateData = {
-    counts: createScoreCounts([10, 20, 30, 40, 50])
-  };
-  it('should get greater than or equal to sum with score 1', () => {
-    const satisfyCriterion = { value: 1 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.greaterThanEqualTo
-      )
-    ).toEqual(150);
-  });
-  it('should get greater than or equal to sum with score 2', () => {
-    const satisfyCriterion = { value: 2 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.greaterThanEqualTo
-      )
-    ).toEqual(140);
-  });
-  it('should get greater than or equal to sum with score 3', () => {
-    const satisfyCriterion = { value: 3 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.greaterThanEqualTo
-      )
-    ).toEqual(120);
-  });
-  it('should get greater than or equal to sum with score 4', () => {
-    const satisfyCriterion = { value: 4 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.greaterThanEqualTo
-      )
-    ).toEqual(90);
-  });
-  it('should get greater than or equal to sum with score 5', () => {
-    const satisfyCriterion = { value: 5 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.greaterThanEqualTo
-      )
-    ).toEqual(50);
-  });
+function expectComparatorResult(satisfyCriterionValue: number, expectedResult: number) {
+  const satisfyCriterion = { value: satisfyCriterionValue };
+  expect(
+    service.getComparatorSum(
+      satisfyCriterion,
+      sampleAggregateData,
+      possibleScoresKi,
+      utilService.greaterThan
+    )
+  ).toEqual(expectedResult);
 }
 
-function getLessThanSum() {
-  const aggregateData = {
-    counts: createScoreCounts([10, 20, 30, 40, 50])
-  };
-  it('should get less than sum with score 2', () => {
-    const satisfyCriterion = { value: 2 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.lessThan
-      )
-    ).toEqual(10);
-  });
-  it('should get less than sum with score 3', () => {
-    const satisfyCriterion = { value: 3 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.lessThan
-      )
-    ).toEqual(30);
-  });
-  it('should get less than sum with score 4', () => {
-    const satisfyCriterion = { value: 4 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.lessThan
-      )
-    ).toEqual(60);
-  });
-  it('should get less than sum with score 5', () => {
-    const satisfyCriterion = { value: 5 };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.lessThan
-      )
-    ).toEqual(100);
+function getComparatorSum_greaterThan0_ReturnSumAll() {
+  it('should get greater than sum with score 0', () => {
+    expectComparatorResult(0, 150);
   });
 }
 
-function getEqualToSum() {
-  it('should return the sum of scores equal to value', () => {
-    const satisfyCriterion = { value: 3 };
-    const aggregateData = {
-      counts: createScoreCounts([10, 20, 30, 40, 50])
-    };
-    expect(
-      service.getComparatorSum(
-        satisfyCriterion,
-        aggregateData,
-        possibleScoresKi,
-        utilService.equalTo
-      )
-    ).toEqual(30);
+function getComparatorSum_greaterThan3_ReturnSumPartial() {
+  it('should get greater than sum with score 3', () => {
+    expectComparatorResult(3, 90);
   });
 }
 
-function getNotEqualToSum() {
-  const aggregateData = {
-    counts: { 1: 2, 2: 0, 3: 1, 4: 0, 5: 0 },
-    scoreCount: 3
-  };
-  it('should return the sum of scores not equal to value', () => {
-    const result = service.getComparatorSum(
-      satisfyCriterionSample,
-      aggregateData,
-      possibleScoresKi,
-      utilService.notEqualTo
-    );
-    expect(result).toBe(2);
+function getComparatorSum_greaterThan5_Return0() {
+  it('should get greater than sum with score 5', () => {
+    expectComparatorResult(5, 0);
   });
 }
 
diff --git a/src/main/webapp/site/src/app/services/user.service.spec.ts b/src/main/webapp/site/src/app/services/user.service.spec.ts
index 5cb27e9c91..8671e76ce5 100644
--- a/src/main/webapp/site/src/app/services/user.service.spec.ts
+++ b/src/main/webapp/site/src/app/services/user.service.spec.ts
@@ -1,8 +1,10 @@
-import { TestBed, inject } from '@angular/core/testing';
+import { fakeAsync, TestBed, tick } from '@angular/core/testing';
 import { UserService } from './user.service';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
 import { ConfigService } from './config.service';
 
+let service: UserService;
+let http: HttpTestingController;
 export class MockConfigService {}
 
 describe('UserService', () => {
@@ -11,9 +13,21 @@ describe('UserService', () => {
       providers: [UserService, { provide: ConfigService, useClass: MockConfigService }],
       imports: [HttpClientTestingModule]
     });
+    service = TestBed.inject(UserService);
+    http = TestBed.inject(HttpTestingController);
   });
+  unlinkGoogleAccount_postToUrl();
+});
 
-  it('should be created', inject([UserService, ConfigService], (service: UserService) => {
-    expect(service).toBeTruthy();
+function unlinkGoogleAccount_postToUrl() {
+  it('unlinkGoogleAccount() should make POST request to unlink google account', fakeAsync(() => {
+    const newPassword = 'my new pass';
+    service.unlinkGoogleUser(newPassword);
+    const unlinkRequest = http.expectOne({
+      url: '/api/google-user/unlink-account',
+      method: 'POST'
+    });
+    unlinkRequest.flush({ response: 'success' });
+    tick();
   }));
-});
+}
diff --git a/src/main/webapp/site/src/app/services/user.service.ts b/src/main/webapp/site/src/app/services/user.service.ts
index 65d7695e52..b419803a08 100644
--- a/src/main/webapp/site/src/app/services/user.service.ts
+++ b/src/main/webapp/site/src/app/services/user.service.ts
@@ -12,13 +12,14 @@ import { Student } from '../domain/student';
 export class UserService {
   private userUrl = '/api/user/info';
   private user$: BehaviorSubject<User> = new BehaviorSubject<User>(null);
-  private checkGoogleUserExistsUrl = '/api/user/check-google-user-exists';
-  private checkGoogleUserMatchesUrl = '/api/user/check-google-user-matches';
-  private googleUserUrl = '/api/user/google-user';
+  private checkGoogleUserExistsUrl = '/api/google-user/check-user-exists';
+  private checkGoogleUserMatchesUrl = '/api/google-user/check-user-matches';
+  private googleUserUrl = '/api/google-user/get-user';
   private checkAuthenticationUrl = '/api/user/check-authentication';
   private changePasswordUrl = '/api/user/password';
   private languagesUrl = '/api/user/languages';
   private contactUrl = '/api/contact';
+  private unlinkGoogleAccountUrl = '/api/google-user/unlink-account';
   isAuthenticated = false;
   isRecaptchaRequired = false;
   redirectUrl: string; // redirect here after logging in
@@ -126,6 +127,17 @@ export class UserService {
     return this.http.get<User>(this.checkGoogleUserMatchesUrl, { params: params });
   }
 
+  unlinkGoogleUser(newPassword: string) {
+    const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
+    let body = new HttpParams();
+    body = body.set('newPassword', newPassword);
+    return this.http
+      .post<any>(this.unlinkGoogleAccountUrl, body, { headers: headers })
+      .subscribe((user) => {
+        this.user$.next(user);
+      });
+  }
+
   getUserByGoogleId(googleUserId: string) {
     let params = new HttpParams();
     params = params.set('googleUserId', googleUserId);
diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html
index e69a78b318..10dd31c105 100644
--- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html
+++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html
@@ -1,5 +1,5 @@
 <form role="form" (submit)="saveChanges()" [formGroup]="editProfileFormGroup">
-  <div fxLayout="column" fxLayoutAlign="start">
+  <div class="inputs">
     <p>
       <mat-form-field fxFlex appearance="fill">
         <mat-label i18n>First Name</mat-label>
@@ -42,15 +42,30 @@
         <mat-error *ngIf="editProfileFormGroup.controls['language'].hasError('required')" i18n>Language required</mat-error>
       </mat-form-field>
     </p>
-    <div>
-      <button mat-raised-button
-              color="primary"
-              type="submit"
-              [disabled]="!editProfileFormGroup.valid || !changed || isSaving"
-              fxFlex
-              fxFlex.gt-xs="0 0 auto">
-        <mat-progress-bar mode="indeterminate" *ngIf="isSaving"></mat-progress-bar>
-        <ng-container i18n>Save Changes</ng-container>
+  </div>
+  <div class="actions"
+      fxLayout="column"
+      fxLayout.gt-sm="row"
+      fxLayoutAlign="center start"
+      fxLayoutAlign.gt-sm="start center"
+      fxLayoutGap="24px">
+    <button mat-raised-button
+        color="primary"
+        type="submit"
+        [disabled]="!editProfileFormGroup.valid || !changed || isSaving">
+      <mat-progress-bar mode="indeterminate" *ngIf="isSaving"></mat-progress-bar>
+      <ng-container i18n>Save Changes</ng-container>
+    </button>
+    <div *ngIf="isGoogleUser"
+        fxLayout="row wrap"
+        fxLayoutAlign="start center"
+        fxLayoutGap="8px">
+      <div fxLayoutAlign="start center" fxLayoutGap="8px">
+        <img class="google-icon" src="assets/img/icons/g-logo.png" i18n-alt alt="Google logo" />
+        <span i18n>This profile is linked to a Google account.</span>
+      </div>
+      <button class="unlink" type="button" mat-raised-button (click)="unlinkGoogleAccount()">
+        <span class="warn" i18n>Unlink Google Account</span>
       </button>
     </div>
   </div>
diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss
index 36ff60ef97..a1e699e818 100644
--- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss
+++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss
@@ -3,10 +3,19 @@
   '~style/abstracts/functions',
   '~style/abstracts/mixins';
 
-form {
+.inputs {
   max-width: breakpoint('sm.min');
+}
+
+.actions {
+  margin-top: 8px;
+}
+
+.google-icon {
+  height: 1.8em;
+  width: auto;
+}
 
-  @media (max-width: breakpoint('sm.max')) {
-    margin: 0 auto;
-  }
+.unlink {
+  margin: 8px 0;
 }
diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts
index c338a4a343..5c425f8766 100644
--- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts
+++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts
@@ -13,6 +13,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
 import { By } from '@angular/platform-browser';
 import { Student } from '../../../domain/student';
 import { configureTestSuite } from 'ng-bullet';
+import { MatDialogModule } from '@angular/material/dialog';
 
 export class MockUserService {
   user: User;
@@ -76,6 +77,7 @@ describe('EditProfileComponent', () => {
       imports: [
         BrowserAnimationsModule,
         ReactiveFormsModule,
+        MatDialogModule,
         MatInputModule,
         MatSelectModule,
         MatSnackBarModule
diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts
index ea256cd802..fe6d7b3452 100644
--- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts
+++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts
@@ -1,22 +1,26 @@
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms';
 import { finalize } from 'rxjs/operators';
 import { MatSnackBar } from '@angular/material/snack-bar';
 import { Student } from '../../../domain/student';
 import { UserService } from '../../../services/user.service';
 import { StudentService } from '../../student.service';
+import { Subscription } from 'rxjs';
+import { MatDialog } from '@angular/material/dialog';
+import { UnlinkGoogleAccountConfirmComponent } from '../../../modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component';
 
 @Component({
   selector: 'app-edit-profile',
   templateUrl: './edit-profile.component.html',
   styleUrls: ['./edit-profile.component.scss']
 })
-export class EditProfileComponent implements OnInit {
+export class EditProfileComponent {
   user: Student;
   languages: object[];
   changed: boolean = false;
   isSaving: boolean = false;
-
+  isGoogleUser: boolean = false;
+  userSubscription: Subscription;
   editProfileFormGroup: FormGroup = this.fb.group({
     firstName: new FormControl({ value: '', disabled: true }, [Validators.required]),
     lastName: new FormControl({ value: '', disabled: true }, [Validators.required]),
@@ -28,6 +32,7 @@ export class EditProfileComponent implements OnInit {
     private fb: FormBuilder,
     private studentService: StudentService,
     private userService: UserService,
+    public dialog: MatDialog,
     public snackBar: MatSnackBar
   ) {
     this.user = <Student>this.getUser().getValue();
@@ -38,10 +43,6 @@ export class EditProfileComponent implements OnInit {
     this.userService.getLanguages().subscribe((response) => {
       this.languages = <object[]>response;
     });
-
-    this.editProfileFormGroup.valueChanges.subscribe(() => {
-      this.changed = true;
-    });
   }
 
   getUser() {
@@ -52,7 +53,18 @@ export class EditProfileComponent implements OnInit {
     this.editProfileFormGroup.controls[name].setValue(value);
   }
 
-  ngOnInit() {}
+  ngOnInit() {
+    this.editProfileFormGroup.valueChanges.subscribe(() => {
+      this.changed = true;
+    });
+    this.userSubscription = this.userService.getUser().subscribe((user) => {
+      this.isGoogleUser = user.isGoogleUser;
+    });
+  }
+
+  ngOnDestroy() {
+    this.userSubscription.unsubscribe();
+  }
 
   saveChanges() {
     this.isSaving = true;
@@ -84,4 +96,10 @@ export class EditProfileComponent implements OnInit {
     }
     this.isSaving = false;
   }
+
+  unlinkGoogleAccount() {
+    this.dialog.open(UnlinkGoogleAccountConfirmComponent, {
+      panelClass: 'mat-dialog--sm'
+    });
+  }
 }
diff --git a/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts b/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts
index e9a9ff545a..3f6c1c6e27 100644
--- a/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts
+++ b/src/main/webapp/site/src/app/teacher-hybrid-angular.module.ts
@@ -51,6 +51,9 @@ import { MultipleChoiceAuthoring } from '../../../wise5/components/multipleChoic
 import { ConceptMapAuthoring } from '../../../wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component';
 import { DrawAuthoring } from '../../../wise5/components/draw/draw-authoring/draw-authoring.component';
 import { MatchAuthoring } from '../../../wise5/components/match/match-authoring/match-authoring.component';
+import { LabelAuthoring } from '../../../wise5/components/label/label-authoring/label-authoring.component';
+import { TableAuthoring } from '../../../wise5/components/table/table-authoring/table-authoring.component';
+import { DiscussionAuthoring } from '../../../wise5/components/discussion/discussion-authoring/discussion-authoring.component';
 
 @NgModule({
   declarations: [
@@ -64,6 +67,7 @@ import { MatchAuthoring } from '../../../wise5/components/match/match-authoring/
     ComponentSelectComponent,
     ConceptMapAuthoring,
     DrawAuthoring,
+    DiscussionAuthoring,
     EditComponentRubricComponent,
     EditComponentJsonComponent,
     EditComponentMaxScoreComponent,
@@ -72,6 +76,7 @@ import { MatchAuthoring } from '../../../wise5/components/match/match-authoring/
     EditHTMLAdvancedComponent,
     EditOutsideUrlAdvancedComponent,
     HtmlAuthoring,
+    LabelAuthoring,
     ManageStudentsComponent,
     MatchAuthoring,
     MilestonesComponent,
@@ -85,6 +90,7 @@ import { MatchAuthoring } from '../../../wise5/components/match/match-authoring/
     RubricAuthoringComponent,
     StatusIconComponent,
     StepInfoComponent,
+    TableAuthoring,
     WorkgroupInfoComponent,
     WorkgroupNodeScoreComponent,
     WorkgroupSelectAutocompleteComponent,
diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html
index b4f5bd0f54..159c38c1b1 100644
--- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html
+++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html
@@ -105,19 +105,36 @@
           </mat-option>
         </mat-select>
         <mat-hint i18n>Help us translate WISE! Visit <a href="https://crowdin.com/project/wise" target="_blank">https://crowdin.com/project/wise</a>.</mat-hint>
-        <mat-error *ngIf="editProfileFormGroup.controls['language'].hasError('required')" i18n>Language required</mat-error>
+        <mat-error *ngIf="editProfileFormGroup.controls['language'].hasError('required')" i18n>
+          Language required
+        </mat-error>
       </mat-form-field>
     </p>
   </div>
-  <div>
+  <div class="actions"
+      fxLayout="column"
+      fxLayout.gt-sm="row"
+      fxLayoutAlign="center start"
+      fxLayoutAlign.gt-sm="start center"
+      fxLayoutGap="24px">
     <button mat-raised-button
             color="primary"
             type="submit"
-            [disabled]="!editProfileFormGroup.valid || !changed || isSaving"
-            fxFlex
-            fxFlex.gt-xs="0 0 auto">
+            [disabled]="!editProfileFormGroup.valid || !changed || isSaving">
       <mat-progress-bar mode="indeterminate" *ngIf="isSaving"></mat-progress-bar>
       <ng-container i18n>Save Changes</ng-container>
     </button>
+    <div *ngIf="isGoogleUser" 
+        fxLayout="row wrap"
+        fxLayoutAlign="start center"
+        fxLayoutGap="8px">
+      <div fxLayoutAlign="start center" fxLayoutGap="8px">
+        <img class="google-icon" src="assets/img/icons/g-logo.png" i18n-alt alt="Google logo" />
+        <span i18n>This profile is linked to a Google account.</span>
+      </div>
+      <button class="unlink" type="button" mat-raised-button (click)="unlinkGoogleAccount()">
+        <span class="warn" i18n>Unlink Google Account</span>
+      </button>
+    </div>
   </div>
 </form>
diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss
index dbd834f0b1..1888291374 100644
--- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss
+++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss
@@ -21,3 +21,16 @@
     }
   }
 }
+
+.actions {
+  margin-top: 8px;
+}
+
+.google-icon {
+  height: 1.8em;
+  width: auto;
+}
+
+.unlink {
+  margin: 8px 0;
+}
diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts
index e8fe132d86..05b8ad23a2 100644
--- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts
+++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts
@@ -13,6 +13,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
 import { By } from '@angular/platform-browser';
 import { User } from '../../../domain/user';
 import { configureTestSuite } from 'ng-bullet';
+import { MatDialogModule } from '@angular/material/dialog';
 
 export class MockUserService {
   user: User;
@@ -89,6 +90,7 @@ describe('EditProfileComponent', () => {
       imports: [
         BrowserAnimationsModule,
         ReactiveFormsModule,
+        MatDialogModule,
         MatInputModule,
         MatSelectModule,
         MatSnackBarModule
diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts
index 6ed9c50c25..01c612ea48 100644
--- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts
+++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts
@@ -1,17 +1,20 @@
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
 import { finalize } from 'rxjs/operators';
 import { MatSnackBar } from '@angular/material/snack-bar';
 import { UserService } from '../../../services/user.service';
 import { Teacher } from '../../../domain/teacher';
 import { TeacherService } from '../../teacher.service';
+import { MatDialog } from '@angular/material/dialog';
+import { UnlinkGoogleAccountConfirmComponent } from '../../../modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component';
+import { Subscription } from 'rxjs';
 
 @Component({
   selector: 'app-edit-profile',
   templateUrl: './edit-profile.component.html',
   styleUrls: ['./edit-profile.component.scss']
 })
-export class EditProfileComponent implements OnInit {
+export class EditProfileComponent {
   user: Teacher;
   schoolLevels: any[] = [
     { id: 'ELEMENTARY_SCHOOL', label: $localize`Elementary School` },
@@ -23,6 +26,8 @@ export class EditProfileComponent implements OnInit {
   languages: object[];
   changed: boolean = false;
   isSaving: boolean = false;
+  isGoogleUser: boolean = false;
+  userSubscription: Subscription;
 
   editProfileFormGroup: FormGroup = this.fb.group({
     firstName: new FormControl({ value: '', disabled: true }, [Validators.required]),
@@ -41,6 +46,7 @@ export class EditProfileComponent implements OnInit {
     private fb: FormBuilder,
     private teacherService: TeacherService,
     private userService: UserService,
+    public dialog: MatDialog,
     public snackBar: MatSnackBar
   ) {
     this.user = <Teacher>this.getUser().getValue();
@@ -57,10 +63,6 @@ export class EditProfileComponent implements OnInit {
     this.userService.getLanguages().subscribe((response) => {
       this.languages = <object[]>response;
     });
-
-    this.editProfileFormGroup.valueChanges.subscribe(() => {
-      this.changed = true;
-    });
   }
 
   getUser() {
@@ -71,7 +73,19 @@ export class EditProfileComponent implements OnInit {
     this.editProfileFormGroup.controls[name].setValue(value);
   }
 
-  ngOnInit() {}
+  ngOnInit() {
+    this.editProfileFormGroup.valueChanges.subscribe(() => {
+      this.changed = true;
+    });
+
+    this.userSubscription = this.userService.getUser().subscribe((user) => {
+      this.isGoogleUser = user.isGoogleUser;
+    });
+  }
+
+  ngOnDestroy() {
+    this.userSubscription.unsubscribe();
+  }
 
   saveChanges() {
     this.isSaving = true;
@@ -128,4 +142,10 @@ export class EditProfileComponent implements OnInit {
       this.snackBar.open($localize`An error occurred. Please try again.`);
     }
   }
+
+  unlinkGoogleAccount() {
+    this.dialog.open(UnlinkGoogleAccountConfirmComponent, {
+      panelClass: 'mat-dialog--sm'
+    });
+  }
 }
diff --git a/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html b/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html
index 1f9c2f19f8..c196fa4e2f 100644
--- a/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html
+++ b/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html
@@ -1,7 +1,7 @@
-<h2 mat-dialog-title fxLayoutAlign="start center" class="warn">
+<h2 mat-dialog-title fxLayoutAlign="start center">
   <span i18n>Edit Classroom Unit</span>
   <span fxFlex></span>
-  <mat-icon color="warn">warning</mat-icon>
+  <mat-icon color="warn" i18n-aria-label aria-label="Warning">warning</mat-icon>
 </h2>
 <mat-dialog-content>
   <div class="info-block">
diff --git a/src/main/webapp/site/src/messages.xlf b/src/main/webapp/site/src/messages.xlf
index 2419779b02..01901da39b 100644
--- a/src/main/webapp/site/src/messages.xlf
+++ b/src/main/webapp/site/src/messages.xlf
@@ -236,29 +236,138 @@
           <context context-type="linenumber">10</context>
         </context-group>
       </trans-unit>
-      <trans-unit datatype="html" id="1ae3dcf96b494406b6fd6fbd2f15f7259553bf4d">
-        <source>Current Password</source>
+      <trans-unit datatype="html" id="692e665f8177ee9150169dca0ff15418616cb3cb">
+        <source>Google logo</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html</context>
+          <context context-type="linenumber">2</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html</context>
+          <context context-type="linenumber">2</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html</context>
+          <context context-type="linenumber">2</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">61</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/register/register-teacher/register-teacher.component.html</context>
+          <context context-type="linenumber">28</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/register/register-teacher-complete/register-teacher-complete.component.html</context>
+          <context context-type="linenumber">14</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/register/register-student-complete/register-student-complete.component.html</context>
+          <context context-type="linenumber">14</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/register/register-student/register-student.component.html</context>
+          <context context-type="linenumber">39</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/register/register-google-user-already-exists/register-google-user-already-exists.component.html</context>
+          <context context-type="linenumber">10</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/student/team-sign-in-dialog/team-sign-in-dialog.component.html</context>
+          <context context-type="linenumber">59</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/student/account/edit-profile/edit-profile.component.html</context>
+          <context context-type="linenumber">64</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/account/edit-profile/edit-profile.component.html</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
-      <trans-unit datatype="html" id="9b79b69a1ba8ba29e68a45acd0829da17da704ce">
-        <source>Current Password required</source>
+      <trans-unit datatype="html" id="47c3500e23cf10b4e86b3c495687d5045fd2305c">
+        <source>Unlink Google Account</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html</context>
+          <context context-type="linenumber">3</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html</context>
+          <context context-type="linenumber">3</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html</context>
+          <context context-type="linenumber">3</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">70</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/student/account/edit-profile/edit-profile.component.html</context>
+          <context context-type="linenumber">68</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/account/edit-profile/edit-profile.component.html</context>
+          <context context-type="linenumber">136</context>
         </context-group>
       </trans-unit>
-      <trans-unit datatype="html" id="0315d84608647a2d4a2b2e7e509d10ad0ec78536">
-        <source>Current Password is incorrect</source>
+      <trans-unit datatype="html" id="09031a48e6666b30516536554bd71ec9073bc906">
+        <source>Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created.</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
-          <context context-type="linenumber">16</context>
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html</context>
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="b3062cd2df40541e781d0e69c02c670cf3c38fa9">
+        <source>Your username is: <x ctype="x-span" equiv-text="&lt;span&gt;" id="START_TAG_SPAN"/><x equiv-text="{{ username }}" id="INTERPOLATION"/><x ctype="x-span" equiv-text="&lt;/span&gt;" id="CLOSE_TAG_SPAN"/>.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html</context>
+          <context context-type="linenumber">8</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="8dd413cee2228118c536f503709329a4d1a395e2">
+        <source>Done</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html</context>
+          <context context-type="linenumber">12</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html</context>
+          <context context-type="linenumber">75</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/create-run-dialog/create-run-dialog.component.html</context>
+          <context context-type="linenumber">85</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/library/share-project-dialog/share-project-dialog.component.html</context>
+          <context context-type="linenumber">70</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/share-run-dialog/share-run-dialog.component.html</context>
+          <context context-type="linenumber">102</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/run-settings-dialog/run-settings-dialog.component.html</context>
+          <context context-type="linenumber">76</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="31874c167522896bc9ac326873703ffde25ede7f">
+        <source>Create a WISE password:</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit datatype="html" id="c7014c6360e94b236286b869c3fe0ea9911c0387">
         <source>New Password</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html</context>
+          <context context-type="linenumber">9</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
           <context context-type="linenumber">22</context>
@@ -266,6 +375,10 @@
       </trans-unit>
       <trans-unit datatype="html" id="ae26b4e37fa94304d1fa1b0c46b10ad979bcecfa">
         <source>New Password required</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html</context>
+          <context context-type="linenumber">16</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
           <context context-type="linenumber">29</context>
@@ -273,6 +386,10 @@
       </trans-unit>
       <trans-unit datatype="html" id="b81fbd1b5d0723eba41870071d02065674bdd565">
         <source>Confirm New Password</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html</context>
+          <context context-type="linenumber">19</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
           <context context-type="linenumber">34</context>
@@ -280,6 +397,10 @@
       </trans-unit>
       <trans-unit datatype="html" id="4b4b336ca43da85eee55391d95fbc03d921353b5">
         <source>Confirm Password required</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
           <context context-type="linenumber">41</context>
@@ -303,11 +424,170 @@
       </trans-unit>
       <trans-unit datatype="html" id="0a9dcaac5aadd48fe716eaff77ab9b21a8bef3af">
         <source>Passwords do not match</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html</context>
+          <context context-type="linenumber">27</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
           <context context-type="linenumber">42</context>
         </context-group>
       </trans-unit>
+      <trans-unit datatype="html" id="d7b35c384aecd25a516200d6921836374613dfe7">
+        <source>Cancel</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html</context>
+          <context context-type="linenumber">31</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html</context>
+          <context context-type="linenumber">14</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/library/copy-project-dialog/copy-project-dialog.component.html</context>
+          <context context-type="linenumber">11</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html</context>
+          <context context-type="linenumber">47</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/create-run-dialog/create-run-dialog.component.html</context>
+          <context context-type="linenumber">63</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/use-with-class-warning-dialog/use-with-class-warning-dialog.component.html</context>
+          <context context-type="linenumber">15</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html</context>
+          <context context-type="linenumber">20</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/student/add-project-dialog/add-project-dialog.component.html</context>
+          <context context-type="linenumber">23</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/student/team-sign-in-dialog/team-sign-in-dialog.component.html</context>
+          <context context-type="linenumber">68</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/share-run-dialog/share-run-dialog.component.html</context>
+          <context context-type="linenumber">100</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/authoring-tool/import-step/choose-import-step/choose-import-step.component.html</context>
+          <context context-type="linenumber">62</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/authoring-tool/import-step/choose-import-step-location/choose-import-step-location.component.html</context>
+          <context context-type="linenumber">40</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/authoring-tool/add-component/choose-new-component/choose-new-component.component.html</context>
+          <context context-type="linenumber">23</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/authoring-tool/add-component/choose-new-component-location/choose-new-component-location.component.html</context>
+          <context context-type="linenumber">41</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html</context>
+          <context context-type="linenumber">37</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/contact/contact-form/contact-form.component.html</context>
+          <context context-type="linenumber">112</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/forgot/student/forgot-student-password/forgot-student-password.component.html</context>
+          <context context-type="linenumber">25</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/forgot/teacher/forgot-teacher-username/forgot-teacher-username.component.html</context>
+          <context context-type="linenumber">25</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/forgot/teacher/forgot-teacher-password/forgot-teacher-password.component.html</context>
+          <context context-type="linenumber">25</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/forgot/student/forgot-student-password-security/forgot-student-password-security.component.html</context>
+          <context context-type="linenumber">28</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.html</context>
+          <context context-type="linenumber">41</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.html</context>
+          <context context-type="linenumber">39</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/forgot/teacher/forgot-teacher-password-verify/forgot-teacher-password-verify.component.html</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="a8059e31694578c1b0344a76a345357dd60e8f01">
+        <source>Warning</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html</context>
+          <context context-type="linenumber">5</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html</context>
+          <context context-type="linenumber">4</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/help/teacher-faq/teacher-faq.component.html</context>
+          <context context-type="linenumber">85</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="5d9fa1b5819208cfbc678e570f4bd34144bcfe81">
+        <source>To remove the link to your Google account, you will be asked to create a WISE password. In the future, you'll sign in to WISE using your username and password.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html</context>
+          <context context-type="linenumber">9</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="f021455ae88703603db9bb34ea44ab9170f37920">
+        <source>You will no longer be able to sign in to WISE using Google. Would you like to continue?</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html</context>
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="ac10a3d9b59575640797c1a8e6aea642cf5d5e77">
+        <source>Continue</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html</context>
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="1ae3dcf96b494406b6fd6fbd2f15f7259553bf4d">
+        <source>Current Password</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
+          <context context-type="linenumber">8</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="9b79b69a1ba8ba29e68a45acd0829da17da704ce">
+        <source>Current Password required</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="0315d84608647a2d4a2b2e7e509d10ad0ec78536">
+        <source>Current Password is incorrect</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit>
       <trans-unit datatype="html" id="5edf054f14aa57bc25a28cb7db01956fee53dac8">
         <source>Change Password</source>
         <context-group purpose="location">
@@ -323,8 +603,8 @@
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit datatype="html" id="1622b67543d6f79dc5e9730e121f7e21b3d034b1">
-        <source>This account was created using Google and doesn't use a WISE password. If you would like to unlink your Google account, please <x ctype="x-a" equiv-text="&lt;a&gt;" id="START_LINK"/>contact us<x ctype="x-a" equiv-text="&lt;/a&gt;" id="CLOSE_LINK"/>.</source>
+      <trans-unit datatype="html" id="8454739bcb4cf4debfd509a35e466a828ea11363">
+        <source><x ctype="image" equiv-text="&lt;img/&gt;" id="TAG_IMG"/><x ctype="x-span" equiv-text="&lt;span&gt;" id="START_TAG_SPAN"/>This account was created using Google and doesn't use a WISE password.<x ctype="x-span" equiv-text="&lt;/span&gt;" id="CLOSE_TAG_SPAN"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/modules/shared/edit-password/edit-password.component.html</context>
           <context context-type="linenumber">60</context>
@@ -1068,57 +1348,6 @@
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit datatype="html" id="d7b35c384aecd25a516200d6921836374613dfe7">
-        <source>Cancel</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/modules/library/copy-project-dialog/copy-project-dialog.component.html</context>
-          <context context-type="linenumber">11</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html</context>
-          <context context-type="linenumber">47</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/teacher/create-run-dialog/create-run-dialog.component.html</context>
-          <context context-type="linenumber">63</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/teacher/use-with-class-warning-dialog/use-with-class-warning-dialog.component.html</context>
-          <context context-type="linenumber">15</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html</context>
-          <context context-type="linenumber">20</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/student/add-project-dialog/add-project-dialog.component.html</context>
-          <context context-type="linenumber">23</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/student/team-sign-in-dialog/team-sign-in-dialog.component.html</context>
-          <context context-type="linenumber">68</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/teacher/share-run-dialog/share-run-dialog.component.html</context>
-          <context context-type="linenumber">100</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/authoring-tool/import-step/choose-import-step/choose-import-step.component.html</context>
-          <context context-type="linenumber">62</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/authoring-tool/import-step/choose-import-step-location/choose-import-step-location.component.html</context>
-          <context context-type="linenumber">40</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/authoring-tool/add-component/choose-new-component/choose-new-component.component.html</context>
-          <context context-type="linenumber">23</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/authoring-tool/add-component/choose-new-component-location/choose-new-component-location.component.html</context>
-          <context context-type="linenumber">41</context>
-        </context-group>
-      </trans-unit>
       <trans-unit datatype="html" id="1979da7460819153e11d2078244645d94291b69c">
         <source>Copy</source>
         <context-group purpose="location">
@@ -1256,29 +1485,6 @@
           <context context-type="linenumber">69</context>
         </context-group>
       </trans-unit>
-      <trans-unit datatype="html" id="8dd413cee2228118c536f503709329a4d1a395e2">
-        <source>Done</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html</context>
-          <context context-type="linenumber">75</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/teacher/create-run-dialog/create-run-dialog.component.html</context>
-          <context context-type="linenumber">85</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/modules/library/share-project-dialog/share-project-dialog.component.html</context>
-          <context context-type="linenumber">70</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/teacher/share-run-dialog/share-run-dialog.component.html</context>
-          <context context-type="linenumber">102</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/teacher/run-settings-dialog/run-settings-dialog.component.html</context>
-          <context context-type="linenumber">76</context>
-        </context-group>
-      </trans-unit>
       <trans-unit datatype="html" id="053c99466a9288207400b49f2ca907192b5bd44d">
         <source>Use with Class</source>
         <context-group purpose="location">
@@ -2294,41 +2500,6 @@
           <context context-type="linenumber">104</context>
         </context-group>
       </trans-unit>
-      <trans-unit datatype="html" id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/contact/contact-form/contact-form.component.html</context>
-          <context context-type="linenumber">112</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/forgot/student/forgot-student-password/forgot-student-password.component.html</context>
-          <context context-type="linenumber">25</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/forgot/teacher/forgot-teacher-username/forgot-teacher-username.component.html</context>
-          <context context-type="linenumber">25</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/forgot/teacher/forgot-teacher-password/forgot-teacher-password.component.html</context>
-          <context context-type="linenumber">25</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/forgot/student/forgot-student-password-security/forgot-student-password-security.component.html</context>
-          <context context-type="linenumber">28</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.html</context>
-          <context context-type="linenumber">41</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.html</context>
-          <context context-type="linenumber">39</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/forgot/teacher/forgot-teacher-password-verify/forgot-teacher-password-verify.component.html</context>
-          <context context-type="linenumber">26</context>
-        </context-group>
-      </trans-unit>
       <trans-unit datatype="html" id="abc0c762d328168e523abf84749240e43593f8df">
         <source>WISE Features</source>
         <context-group purpose="location">
@@ -3850,13 +4021,6 @@
           <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
-      <trans-unit datatype="html" id="a8059e31694578c1b0344a76a345357dd60e8f01">
-        <source>Warning</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/help/teacher-faq/teacher-faq.component.html</context>
-          <context context-type="linenumber">85</context>
-        </context-group>
-      </trans-unit>
       <trans-unit datatype="html" id="4ad84d4f3bbc9a37b27cd1ff908aea5fa83dccf3">
         <source>If you move a student to a different period, they will lose all of their work.</source>
         <context-group purpose="location">
@@ -4563,50 +4727,23 @@
           <context context-type="sourcefile">app/register/register-teacher-form/register-teacher-form.component.html</context>
           <context context-type="linenumber">7</context>
         </context-group>
-      </trans-unit>
-      <trans-unit datatype="html" id="28629af803ab8b5f6e995b17d486e6a1527e2a96">
-        <source>Sign Up</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/register/register-teacher/register-teacher.component.html</context>
-          <context context-type="linenumber">18</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/register/register-student/register-student.component.html</context>
-          <context context-type="linenumber">29</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit datatype="html" id="223846e3b440a5bf278a46411d71586ac79cc6ec">
-        <source>- or -</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/register/register-teacher/register-teacher.component.html</context>
-          <context context-type="linenumber">22</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit datatype="html" id="692e665f8177ee9150169dca0ff15418616cb3cb">
-        <source>Google logo</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/register/register-teacher/register-teacher.component.html</context>
-          <context context-type="linenumber">28</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/register/register-teacher-complete/register-teacher-complete.component.html</context>
-          <context context-type="linenumber">14</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/register/register-student-complete/register-student-complete.component.html</context>
-          <context context-type="linenumber">14</context>
-        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="28629af803ab8b5f6e995b17d486e6a1527e2a96">
+        <source>Sign Up</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">app/register/register-student/register-student.component.html</context>
-          <context context-type="linenumber">39</context>
+          <context context-type="sourcefile">app/register/register-teacher/register-teacher.component.html</context>
+          <context context-type="linenumber">18</context>
         </context-group>
         <context-group purpose="location">
-          <context context-type="sourcefile">app/register/register-google-user-already-exists/register-google-user-already-exists.component.html</context>
-          <context context-type="linenumber">10</context>
+          <context context-type="sourcefile">app/register/register-student/register-student.component.html</context>
+          <context context-type="linenumber">29</context>
         </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="223846e3b440a5bf278a46411d71586ac79cc6ec">
+        <source>- or -</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">app/student/team-sign-in-dialog/team-sign-in-dialog.component.html</context>
-          <context context-type="linenumber">59</context>
+          <context context-type="sourcefile">app/register/register-teacher/register-teacher.component.html</context>
+          <context context-type="linenumber">22</context>
         </context-group>
       </trans-unit>
       <trans-unit datatype="html" id="49ddce3277c9c81865f8d2278255426cdc9e94f2">
@@ -5290,20 +5427,27 @@
           <context context-type="sourcefile">app/student/account/edit-profile/edit-profile.component.html</context>
           <context context-type="linenumber">42</context>
         </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="afa960379d26eb20fd22e6e10537c1c5ec74c5d1">
+        <source>Save Changes</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/student/account/edit-profile/edit-profile.component.html</context>
+          <context context-type="linenumber">57</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/teacher/account/edit-profile/edit-profile.component.html</context>
-          <context context-type="linenumber">108</context>
+          <context context-type="linenumber">125</context>
         </context-group>
       </trans-unit>
-      <trans-unit datatype="html" id="afa960379d26eb20fd22e6e10537c1c5ec74c5d1">
-        <source>Save Changes</source>
+      <trans-unit datatype="html" id="edb53ba7560c1571d41ff16d93805243e5264d70">
+        <source>This profile is linked to a Google account.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/student/account/edit-profile/edit-profile.component.html</context>
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">65</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/teacher/account/edit-profile/edit-profile.component.html</context>
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">133</context>
         </context-group>
       </trans-unit>
       <trans-unit datatype="html" id="2e1aeaf8d183e6846a0463a08c784f472184fcf0">
@@ -5804,6 +5948,13 @@
           <context context-type="linenumber">107</context>
         </context-group>
       </trans-unit>
+      <trans-unit datatype="html" id="2b2f9f56dbfbfb71056a8da8c94e8011d837766e">
+        <source> Language required </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/teacher/account/edit-profile/edit-profile.component.html</context>
+          <context context-type="linenumber">108</context>
+        </context-group>
+      </trans-unit>
       <trans-unit datatype="html" id="44fcb249525dd82d1fd32cae51412bcda7ffd765">
         <source>Back to Unit Plan</source>
         <context-group purpose="location">
@@ -6024,6 +6175,14 @@
           <context context-type="sourcefile">../../wise5/components/draw/draw-authoring/draw-authoring.component.html</context>
           <context context-type="linenumber">2</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/discussion/discussion-authoring/discussion-authoring.component.html</context>
+          <context context-type="linenumber">2</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">2</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">../../wise5/components/match/match-authoring/match-authoring.component.html</context>
           <context context-type="linenumber">2</context>
@@ -6036,6 +6195,10 @@
           <context context-type="sourcefile">../../wise5/components/openResponse/open-response-authoring/open-response-authoring.component.html</context>
           <context context-type="linenumber">11</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">2</context>
+        </context-group>
       </trans-unit>
       <trans-unit datatype="html" id="495b53dee6164e1c3ec7d95ee3527bb23b62a8c2">
         <source>Enter Prompt Here</source>
@@ -6047,6 +6210,14 @@
           <context context-type="sourcefile">../../wise5/components/draw/draw-authoring/draw-authoring.component.html</context>
           <context context-type="linenumber">6</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/discussion/discussion-authoring/discussion-authoring.component.html</context>
+          <context context-type="linenumber">6</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">6</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">../../wise5/components/match/match-authoring/match-authoring.component.html</context>
           <context context-type="linenumber">6</context>
@@ -6059,6 +6230,10 @@
           <context context-type="sourcefile">../../wise5/components/openResponse/open-response-authoring/open-response-authoring.component.html</context>
           <context context-type="linenumber">15</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">6</context>
+        </context-group>
       </trans-unit>
       <trans-unit datatype="html" id="818444ddcd112e756ab725357dcfb238b9257324">
         <source>Background Image (Optional)</source>
@@ -6105,6 +6280,10 @@
           <context context-type="sourcefile">../../wise5/components/draw/draw-authoring/draw-authoring.component.html</context>
           <context context-type="linenumber">228</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">21</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">../../wise5/components/match/match-authoring/match-authoring.component.html</context>
           <context context-type="linenumber">41</context>
@@ -6189,6 +6368,10 @@
           <context context-type="sourcefile">../../wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.html</context>
           <context context-type="linenumber">87</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">24</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">../../wise5/components/multipleChoice/multiple-choice-authoring/multiple-choice-authoring.component.html</context>
           <context context-type="linenumber">68</context>
@@ -6316,6 +6499,14 @@
           <context context-type="sourcefile">app/authoring-tool/edit-component-tags/edit-component-tags.component.html</context>
           <context context-type="linenumber">38</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">206</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">232</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">../../wise5/components/match/match-authoring/match-authoring.component.html</context>
           <context context-type="linenumber">79</context>
@@ -6332,6 +6523,14 @@
           <context context-type="sourcefile">../../wise5/components/multipleChoice/multiple-choice-authoring/multiple-choice-authoring.component.html</context>
           <context context-type="linenumber">124</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">61</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">102</context>
+        </context-group>
       </trans-unit>
       <trans-unit datatype="html" id="e3b469b69eef004764c51b7a8b3efa761501ab73">
         <source> Show Node Labels </source>
@@ -6378,6 +6577,10 @@
           <context context-type="sourcefile">../../wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.html</context>
           <context context-type="linenumber">195</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">128</context>
+        </context-group>
       </trans-unit>
       <trans-unit datatype="html" id="9fae747b3706d7f4d6b694ae273d7df2799a732c">
         <source>(Optional) Create a starting state for the concept map by editing the "Student Preview" below and then saving here:</source>
@@ -6613,6 +6816,10 @@
           <context context-type="sourcefile">../../wise5/components/draw/draw-authoring/draw-authoring.component.html</context>
           <context context-type="linenumber">277</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">221</context>
+        </context-group>
       </trans-unit>
       <trans-unit datatype="html" id="a5a5b66acab96c55cf12967a05d09f0ad6524482">
         <source>Delete Starter Drawing</source>
@@ -6621,6 +6828,20 @@
           <context context-type="linenumber">285</context>
         </context-group>
       </trans-unit>
+      <trans-unit datatype="html" id="1037affb09c3a77f83838b47e805b78b82314e6c">
+        <source> Students can upload and use images in their posts </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/discussion/discussion-authoring/discussion-authoring.component.html</context>
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="4b10143a37c7361d1cc9c3ffc72e792e99ffebda">
+        <source> Students must create a post before viewing classmates' posts </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/discussion/discussion-authoring/discussion-authoring.component.html</context>
+          <context context-type="linenumber">25</context>
+        </context-group>
+      </trans-unit>
       <trans-unit datatype="html" id="7affaae0e14529c0192d5a4bfabd6fcb1ea27529">
         <source>Rubric</source>
         <context-group purpose="location">
@@ -6691,6 +6912,196 @@
           <context context-type="linenumber">2</context>
         </context-group>
       </trans-unit>
+      <trans-unit datatype="html" id="b8c5c01ab5965f003ba27c1aad429045431ba260">
+        <source>Background Image</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="33fa790e0ddac9013da2c3c2016b0c015fc20e6c">
+        <source>Canvas Width (px)</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">31</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="579bd990c53c37a00d6f2be19b772c2bd901b5eb">
+        <source>Canvas Height (px)</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">38</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="19858049294089faa3ac7a89cd9fd8cb7c0cb084">
+        <source>Point Radius Size (px)</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">47</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="0bd1b7ca428a0c47d189e1f26194062b13a0aa6c">
+        <source>Font Size</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">54</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="121f675f133037351ec1ab755f28255087d9a8d0">
+        <source>Label Max Character Width</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">61</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="a810bf354768354951108e32fa7e876345690b41">
+        <source> Can Student Create Labels </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">74</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="ba37ce872dd59293b716f4aab9ffa2dcacef8720">
+        <source> Enable Dots </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">83</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="f89b35f27f0d1b5e3a9d45a5440a7646af2499b9">
+        <source> Allow Student to Upload Image for Background </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">92</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="720864f262b53e2fff90515031217f60a6f6f6fc">
+        <source>Starter Labels</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">99</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="c731b40da1d127154343d68fc55d26306861ac21">
+        <source>Add Starter Label</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">103</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="a11ec4e640f241329943237d2a3cceaa4823658e">
+        <source>add</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">106</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="576fe3dc92e73a8a329cf2fbd1ec8557b37446e1">
+        <source> There are no starter labels. Click the "Add Label" button to add a starter label. </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">113</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="8ba143716c2da6e4120c0c1a804f0bdd9a7e5f5b">
+        <source>Text</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">120</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="8cf83753f4aa4dcef3ace7c49f8501c15d2951d2">
+        <source>Enter Label Text Here</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">124</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="5040677be611ec050369ecb2b9c1501219b9a5d1">
+        <source>View Colors</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">137</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="e534bce868110db32f1c6d91a123548048dc66c1">
+        <source>Color Palette</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">140</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="b2170964421e8fb291a89d020602cc92cb4eaf56">
+        <source> Can Student Edit Label </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">151</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="5005a394c03499dd6211c362e47d7d1c6987a020">
+        <source> Can Student Delete Label </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">160</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="d70bab20ade1a98b907f11f54c14a2c9b9012605">
+        <source>Point Location</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">167</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="eb652ec6e5b2ef867985722c158224053cca15ab">
+        <source>X</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">169</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">186</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="5f735468df8e1a39a74409adff2c9a9a5f29ead8">
+        <source>Y</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">176</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">193</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="054b7124def3fc0eacfb779c0d98a6dc4dec2ae3">
+        <source>Text Location</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">184</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="b5b4ee55539c3229b8eb1f201029aeffcbbd0841">
+        <source>Delete Label</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">203</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="f1e5600c2fa5270b62ebe32c80557986dd7e1fc0">
+        <source>Save Starter Labels</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">218</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="ce8236188975ede0677402fb75456a3db8499613">
+        <source>Delete Starter Labels</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/label/label-authoring/label-authoring.component.html</context>
+          <context context-type="linenumber">229</context>
+        </context-group>
+      </trans-unit>
       <trans-unit datatype="html" id="e599864c0a890e5dfad60da9e9ed9f53ca9893bb">
         <source>Choices</source>
         <context-group purpose="location">
@@ -7067,6 +7478,113 @@
           <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
+      <trans-unit datatype="html" id="b169576ab898458aa823e09c1e7f3397f8f011a0">
+        <source>Columns</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="79765b4b2ed87b05e0fd5d6937cec32d1995645f">
+        <source>Rows</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">21</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="aa22ffb76fc0555efcd54b896bff75a3e469982e">
+        <source>Global Cell Size</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">29</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="630090c98b3bf8c12d948c30ea150a25bc861fe9">
+        <source>Insert Column Before</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">47</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">50</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="9634f970a787b967a6da5d3edcd6d6530012278c">
+        <source>Delete Column</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">58</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="97537cb70f46603f1ae244ad748fa2622fb5bf0b">
+        <source>Insert Column After</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">70</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">73</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="897363afbaacb7374bb5e259129b76687b431671">
+        <source>Insert Row Before</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">88</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">91</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="2ab75889a800e0e0fd14d0d63b8a003ee69519c6">
+        <source>Delete Row</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">99</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="75e10460c25b4a5f95b9d8077f0cb9b8968b47ce">
+        <source>Insert Row After</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">111</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">114</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="13968818aa5f7771fe043f7d614f21c66b21e21a">
+        <source> Editable </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">133</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="5943b22e7e12f461de18c831b7cf94ddf4baf11a">
+        <source>Column Cell Size</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">155</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="8d3659d1c474ee37dde6c775445b585af3b79b56">
+        <source> Make All Cells Editable </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">171</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit datatype="html" id="964c339de395a09a2ea31889d1f3b0e89ffd9107">
+        <source> Make All Cells Uneditable </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">../../wise5/components/table/table-authoring/table-authoring.component.html</context>
+          <context context-type="linenumber">178</context>
+        </context-group>
+      </trans-unit>
       <trans-unit datatype="html" id="f9c52903219e583ddefb23cd102d2057504ac980">
         <source>(Team <x equiv-text="{{workgroupId}}" id="INTERPOLATION"/>)</source>
         <context-group purpose="location">
diff --git a/src/main/webapp/site/src/style/layout/_section.scss b/src/main/webapp/site/src/style/layout/_section.scss
index bc80d059f1..25b5b018a6 100644
--- a/src/main/webapp/site/src/style/layout/_section.scss
+++ b/src/main/webapp/site/src/style/layout/_section.scss
@@ -20,7 +20,7 @@
 }
 
 .section__tab {
-  padding: 24px 0;
+  padding: 24px 4px;
 
   @media (min-width: breakpoint('sm.min')) {
     padding: 24px 16px;
diff --git a/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts b/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts
index 6634abd0cc..67ae4403ea 100644
--- a/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts
+++ b/src/main/webapp/wise5/authoringTool/components/component-authoring.component.ts
@@ -62,6 +62,11 @@ export abstract class ComponentAuthoring {
     );
   }
 
+  ngOnDestroy() {
+    this.componentChangedSubscription.unsubscribe();
+    this.starterStateResponseSubscription.unsubscribe();
+  }
+
   promptChanged(prompt: string): void {
     this.promptChange.next(prompt);
   }
@@ -103,6 +108,16 @@ export abstract class ComponentAuthoring {
     });
   }
 
+  chooseBackgroundImage(): void {
+    const params = {
+      isPopup: true,
+      nodeId: this.nodeId,
+      componentId: this.componentId,
+      target: 'background'
+    };
+    this.openAssetChooser(params);
+  }
+
   openAssetChooser(params: any): any {
     return this.ProjectAssetService.openAssetChooser(params).then((data: any) => {
       return this.assetSelected(data);
diff --git a/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.html b/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.html
new file mode 100644
index 0000000000..986833bfc1
--- /dev/null
+++ b/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.html
@@ -0,0 +1,21 @@
+<md-dialog class="dialog--wider" aria-label="{{ 'MILESTONE_DETAILS_TITLE' | translate : { name: $ctrl.milestone.name } }}">
+  <md-toolbar>
+    <div class="md-toolbar-tools">
+      <h2>{{ 'MILESTONE_DETAILS_TITLE' | translate : { name: $ctrl.milestone.name } }}</h2>
+    </div>
+  </md-toolbar>
+  <md-dialog-content class="gray-lighter-bg md-dialog-content">
+    <milestone-details milestone="$ctrl.milestone"
+                       hide-student-work="$ctrl.hideStudentWork"
+                       on-show-workgroup="$ctrl.onShowWorkgroup(value)"
+                       on-visit-node-grading="$ctrl.onVisitNodeGrading()"></milestone-details>
+  </md-dialog-content>
+  <md-dialog-actions layout="row" layout-align="start center">
+    <span flex></span>
+    <md-button class="md-primary"
+               ng-click="$ctrl.close()"
+               aria-label="{{ ::'CLOSE' | translate }}">
+      {{ ::'CLOSE' | translate }}
+    </md-button>
+  </md-dialog-actions>
+</md-dialog>
\ No newline at end of file
diff --git a/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.ts b/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.ts
new file mode 100644
index 0000000000..eaec3e2d0b
--- /dev/null
+++ b/src/main/webapp/wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.ts
@@ -0,0 +1,77 @@
+export class MilestoneDetailsDialog {
+  title: string;
+
+  static $inject = [
+    '$state',
+    '$mdDialog',
+    '$event',
+    'milestone',
+    'hideStudentWork',
+    'TeacherDataService'
+  ];
+
+  constructor(
+    private $state,
+    private $mdDialog,
+    private $event,
+    private milestone,
+    private hideStudentWork,
+    private TeacherDataService
+  ) {}
+
+  $onInit() {
+    this.saveMilestoneOpenedEvent();
+  }
+
+  close() {
+    this.saveMilestoneClosedEvent();
+    this.$mdDialog.hide();
+  }
+
+  edit() {
+    this.$mdDialog.hide({
+      milestone: this.milestone,
+      action: 'edit',
+      $event: this.$event
+    });
+  }
+
+  onShowWorkgroup(workgroup: any) {
+    this.saveMilestoneClosedEvent();
+    this.$mdDialog.hide();
+    this.TeacherDataService.setCurrentWorkgroup(workgroup);
+    this.$state.go('root.nodeProgress');
+  }
+
+  onVisitNodeGrading() {
+    this.$mdDialog.hide();
+  }
+
+  saveMilestoneOpenedEvent() {
+    this.saveMilestoneEvent('MilestoneOpened');
+  }
+
+  saveMilestoneClosedEvent() {
+    this.saveMilestoneEvent('MilestoneClosed');
+  }
+
+  saveMilestoneEvent(event: any) {
+    const context = 'ClassroomMonitor',
+      nodeId = null,
+      componentId = null,
+      componentType = null,
+      category = 'Navigation',
+      data = { milestoneId: this.milestone.id },
+      projectId = null;
+    this.TeacherDataService.saveEvent(
+      context,
+      nodeId,
+      componentId,
+      componentType,
+      category,
+      event,
+      data,
+      projectId
+    );
+  }
+}
diff --git a/src/main/webapp/wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.ts b/src/main/webapp/wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.ts
index 5a140470a4..9fbf33365e 100644
--- a/src/main/webapp/wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.ts
+++ b/src/main/webapp/wise5/components/conceptMap/concept-map-authoring/concept-map-authoring.component.ts
@@ -156,16 +156,6 @@ export class ConceptMapAuthoring extends ComponentAuthoring {
     }
   }
 
-  chooseBackgroundImage(): void {
-    const params = {
-      isPopup: true,
-      nodeId: this.nodeId,
-      componentId: this.componentId,
-      target: 'background'
-    };
-    this.openAssetChooser(params);
-  }
-
   chooseNodeImage(conceptMapNodeId: string): void {
     const params = {
       isPopup: true,
diff --git a/src/main/webapp/wise5/components/discussion/authoring.html b/src/main/webapp/wise5/components/discussion/authoring.html
deleted file mode 100644
index 1c169b45ce..0000000000
--- a/src/main/webapp/wise5/components/discussion/authoring.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<div>
-  <div ng-if='!discussionController.authoringComponentContent.showPreviousWork'>
-    <md-input-container style='width: 100%; margin-bottom: 0;'>
-      <label>{{ ::'PROMPT' | translate }}</label>
-      <textarea rows='1'
-                ng-model='discussionController.authoringComponentContent.prompt'
-                ng-change='discussionController.componentChanged()'
-                ng-model-options='{ debounce: 1000 }'
-                placeholder='{{ ::"enterPromptHere" | translate }}'>
-    </textarea>
-    </md-input-container>
-    <md-input-container style='margin-top: 0; margin-bottom: 0;'>
-      <md-checkbox class='md-primary'
-                    ng-model='discussionController.authoringComponentContent.isStudentAttachmentEnabled'
-                    ng-change='discussionController.componentChanged()'>
-        {{ ::'discussion.allowUploadedImagesInPosts' | translate }}
-      </md-checkbox>
-    </md-input-container>
-    <br/>
-    <md-input-container style='margin-top: 0; margin-bottom: 0;'>
-      <md-checkbox class='md-primary'
-                   ng-model='discussionController.authoringComponentContent.gateClassmateResponses'
-                   ng-change='discussionController.componentChanged()'>
-        {{ ::'discussion.gateClassmateResponses' | translate }}
-      </md-checkbox>
-    </md-input-container>
-  </div>
-</div>
diff --git a/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.html b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.html
new file mode 100644
index 0000000000..d457e04955
--- /dev/null
+++ b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.html
@@ -0,0 +1,28 @@
+<mat-form-field class="prompt">
+  <mat-label i18n>Prompt</mat-label>
+  <textarea matInput
+      [ngModel]="authoringComponentContent.prompt"
+      (ngModelChange)="promptChanged($event)"
+      placeholder="Enter Prompt Here"
+      i18n-placeholder
+      cdkTextareaAutosize>
+  </textarea>
+</mat-form-field>
+<div class="checkbox-container">
+  <mat-checkbox
+      color="primary"
+      [(ngModel)]="authoringComponentContent.isStudentAttachmentEnabled"
+      (ngModelChange)="componentChanged()"
+      i18n>
+    Students can upload and use images in their posts
+  </mat-checkbox>
+</div>
+<div class="checkbox-container">
+  <mat-checkbox
+      color="primary"
+      [(ngModel)]="authoringComponentContent.gateClassmateResponses"
+      (ngModelChange)="componentChanged()"
+      i18n>
+    Students must create a post before viewing classmates' posts
+  </mat-checkbox>
+</div>
\ No newline at end of file
diff --git a/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.scss b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.scss
new file mode 100644
index 0000000000..7221a74cf4
--- /dev/null
+++ b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.scss
@@ -0,0 +1,8 @@
+.prompt {
+  width: 100%;
+}
+
+.checkbox-container {
+  margin-top: 5px;
+  margin-bottom: 15px;
+}
\ No newline at end of file
diff --git a/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.ts b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.ts
new file mode 100644
index 0000000000..ce81455ece
--- /dev/null
+++ b/src/main/webapp/wise5/components/discussion/discussion-authoring/discussion-authoring.component.ts
@@ -0,0 +1,24 @@
+'use strict';
+
+import { Component } from '@angular/core';
+import { ProjectAssetService } from '../../../../site/src/app/services/projectAssetService';
+import { ComponentAuthoring } from '../../../authoringTool/components/component-authoring.component';
+import { ConfigService } from '../../../services/configService';
+import { NodeService } from '../../../services/nodeService';
+import { TeacherProjectService } from '../../../services/teacherProjectService';
+
+@Component({
+  selector: 'discussion-authoring',
+  templateUrl: 'discussion-authoring.component.html',
+  styleUrls: ['discussion-authoring.component.scss']
+})
+export class DiscussionAuthoring extends ComponentAuthoring {
+  constructor(
+    protected ConfigService: ConfigService,
+    protected NodeService: NodeService,
+    protected ProjectAssetService: ProjectAssetService,
+    protected ProjectService: TeacherProjectService
+  ) {
+    super(ConfigService, NodeService, ProjectAssetService, ProjectService);
+  }
+}
diff --git a/src/main/webapp/wise5/components/discussion/discussionAuthoring.ts b/src/main/webapp/wise5/components/discussion/discussionAuthoring.ts
deleted file mode 100644
index d30b4b0c2f..0000000000
--- a/src/main/webapp/wise5/components/discussion/discussionAuthoring.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-'use strict';
-
-import { Directive } from '@angular/core';
-import { EditComponentController } from '../../authoringTool/components/editComponentController';
-
-@Directive()
-class DiscussionAuthoringController extends EditComponentController {
-  static $inject = [
-    '$filter',
-    'ConfigService',
-    'NodeService',
-    'NotificationService',
-    'ProjectAssetService',
-    'ProjectService',
-    'UtilService'
-  ];
-
-  constructor(
-    $filter,
-    ConfigService,
-    NodeService,
-    NotificationService,
-    ProjectAssetService,
-    ProjectService,
-    UtilService
-  ) {
-    super(
-      $filter,
-      ConfigService,
-      NodeService,
-      NotificationService,
-      ProjectAssetService,
-      ProjectService,
-      UtilService
-    );
-  }
-}
-
-const DiscussionAuthoring = {
-  bindings: {
-    nodeId: '@',
-    componentId: '@'
-  },
-  controller: DiscussionAuthoringController,
-  controllerAs: 'discussionController',
-  templateUrl: 'wise5/components/discussion/authoring.html'
-};
-
-export default DiscussionAuthoring;
diff --git a/src/main/webapp/wise5/components/discussion/discussionAuthoringComponentModule.ts b/src/main/webapp/wise5/components/discussion/discussionAuthoringComponentModule.ts
index 77bf783b60..9acf77bcbe 100644
--- a/src/main/webapp/wise5/components/discussion/discussionAuthoringComponentModule.ts
+++ b/src/main/webapp/wise5/components/discussion/discussionAuthoringComponentModule.ts
@@ -1,16 +1,19 @@
 'use strict';
 
 import * as angular from 'angular';
-import { downgradeInjectable } from '@angular/upgrade/static';
+import { downgradeComponent, downgradeInjectable } from '@angular/upgrade/static';
 import { DiscussionService } from './discussionService';
-import DiscussionAuthoring from './discussionAuthoring';
 import { EditDiscussionAdvancedComponent } from './edit-discussion-advanced/edit-discussion-advanced.component';
+import { DiscussionAuthoring } from './discussion-authoring/discussion-authoring.component';
 
 const discussionAuthoringComponentModule = angular
   .module('discussionAuthoringComponentModule', ['pascalprecht.translate'])
   .service('DiscussionService', downgradeInjectable(DiscussionService))
-  .component('discussionAuthoring', DiscussionAuthoring)
   .component('editDiscussionAdvanced', EditDiscussionAdvancedComponent)
+  .directive(
+    'discussionAuthoring',
+    downgradeComponent({ component: DiscussionAuthoring }) as angular.IDirectiveFactory
+  )
   .config([
     '$translatePartialLoaderProvider',
     ($translatePartialLoaderProvider) => {
diff --git a/src/main/webapp/wise5/components/discussion/discussionService.ts b/src/main/webapp/wise5/components/discussion/discussionService.ts
index 7249536a45..78467bf01e 100644
--- a/src/main/webapp/wise5/components/discussion/discussionService.ts
+++ b/src/main/webapp/wise5/components/discussion/discussionService.ts
@@ -39,7 +39,7 @@ export class DiscussionService extends ComponentService {
   createComponent() {
     const component: any = super.createComponent();
     component.type = 'Discussion';
-    component.prompt = this.getTranslation('ENTER_PROMPT_HERE');
+    component.prompt = '';
     component.isStudentAttachmentEnabled = true;
     component.gateClassmateResponses = true;
     return component;
diff --git a/src/main/webapp/wise5/components/draw/draw-authoring/draw-authoring.component.ts b/src/main/webapp/wise5/components/draw/draw-authoring/draw-authoring.component.ts
index 930150944a..c17659ba9a 100644
--- a/src/main/webapp/wise5/components/draw/draw-authoring/draw-authoring.component.ts
+++ b/src/main/webapp/wise5/components/draw/draw-authoring/draw-authoring.component.ts
@@ -176,16 +176,6 @@ export class DrawAuthoring extends ComponentAuthoring {
     this.componentChanged();
   }
 
-  chooseBackgroundImage(): void {
-    const params = {
-      isPopup: true,
-      nodeId: this.nodeId,
-      componentId: this.componentId,
-      target: 'background'
-    };
-    this.openAssetChooser(params);
-  }
-
   chooseStampImage(stampIndex: number): void {
     const params = {
       isPopup: true,
diff --git a/src/main/webapp/wise5/components/label/authoring.html b/src/main/webapp/wise5/components/label/authoring.html
deleted file mode 100644
index 2e53f91e8a..0000000000
--- a/src/main/webapp/wise5/components/label/authoring.html
+++ /dev/null
@@ -1,373 +0,0 @@
-<style>
-  {{labelController.nodeContent.style}}
-  .studentButton {
-    min-width: 50px;
-    max-width: 50px;
-  }
-</style>
-
-<div flex>
-  <div class='advancedAuthoringDiv'
-       ng-if='labelController.showAdvancedAuthoring'>
-    <div>
-      <md-checkbox class='md-primary'
-                   ng-model='labelController.authoringComponentContent.showSaveButton'
-                   ng-change='labelController.componentChanged()'>
-        {{ ::'SHOW_SAVE_BUTTON' | translate }}
-      </md-checkbox>
-    </div>
-    <div>
-      <md-checkbox class='md-primary'
-                   ng-model='labelController.authoringComponentContent.showSubmitButton'
-                   ng-change='labelController.componentChanged()'>
-        {{ ::'SHOW_SUBMIT_BUTTON' | translate }}
-      </md-checkbox>
-    </div>
-    <div>
-      <md-checkbox class='md-primary'
-                   ng-if='labelController.isNotebookEnabled()'
-                   ng-model='labelController.authoringComponentContent.showAddToNotebookButton'
-                   ng-change='labelController.componentChanged()'>
-        {{ ::'SHOW_ADD_TO_NOTEBOOK_BUTTON' | translate }}
-      </md-checkbox>
-    </div>
-    <div>
-      <md-input-container style='margin-right: 20px; width: 150px; height: 25px;'
-                          ng-if='labelController.authoringComponentContent.showSubmitButton'>
-        <label>{{ ::'MAX_SUBMIT' | translate }}</label>
-        <input type='number'
-               ng-model='labelController.authoringComponentContent.maxSubmitCount'
-               ng-model-options='{ debounce: 1000 }'
-               ng-change='labelController.componentChanged()'/>
-      </md-input-container>
-    </div>
-    <div layout="column" layout-align="start start">
-      <edit-component-max-score [authoring-component-content]="labelController.authoringComponentContent"></edit-component-max-score>
-      <edit-component-width [authoring-component-content]="labelController.authoringComponentContent"></edit-component-width>
-      <edit-component-rubric [authoring-component-content]="labelController.authoringComponentContent"></edit-component-rubric>
-      <edit-component-tags [authoring-component-content]="labelController.authoringComponentContent"></edit-component-tags>
-    </div>
-    <div>
-      <div>
-        <label class='node__label--vertical-alignment'>
-          {{ ::'CONNECTED_COMPONENTS' | translate }}
-        </label>
-        <md-button class='topButton md-raised md-primary'
-                   ng-click='labelController.addConnectedComponent()'>
-          <md-icon>add</md-icon>
-          <md-tooltip md-direction='top'
-                      class='projectButtonTooltip'>
-            {{ ::'ADD_CONNECTED_COMPONENT' | translate }}
-          </md-tooltip>
-        </md-button>
-      </div>
-      <div ng-repeat='connectedComponent in labelController.authoringComponentContent.connectedComponents track by $index'
-           style='border: 2px solid #dddddd; border-radius: 5px; margin-bottom: 10px; padding: 20px 20px 10px 20px;'>
-        <div flex>
-          <md-input-container style='margin-right: 20px; width: 300px;'>
-            <label>{{ ::'step' | translate }}</label>
-            <md-select ng-model='connectedComponent.nodeId'
-                       ng-change='labelController.connectedComponentNodeIdChanged(connectedComponent)'
-                       style='width: 300px'>
-              <md-option ng-repeat='item in labelController.idToOrder | toArray | orderBy : "order"'
-                         value='{{item.$key}}'
-                         ng-if='labelController.isApplicationNode(item.$key)'>
-                {{ labelController.getNodePositionAndTitleByNodeId(item.$key) }}
-              </md-option>
-            </md-select>
-          </md-input-container>
-          <md-input-container style='margin-right: 20px; width: 300px;'>
-            <label>{{ ::'component' | translate }}</label>
-            <md-select ng-model='connectedComponent.componentId'
-                       ng-change='labelController.connectedComponentComponentIdChanged(connectedComponent)'
-                       style='width: 300px'>
-              <md-option ng-repeat='(componentIndex, component) in labelController.getComponentsByNodeId(connectedComponent.nodeId)'
-                         value='{{component.id}}'
-                         ng-disabled='!labelController.isConnectedComponentTypeAllowed(component.type) || component.id == labelController.componentId'>
-                {{ componentIndex + 1 }}. {{ component.type }}
-                <span ng-if='component.id == labelController.componentId'>
-                  ({{ ::'thisComponent' | translate }})
-                </span>
-              </md-option>
-            </md-select>
-          </md-input-container>
-          <md-input-container style='margin-right: 20px; width: 200px;'>
-            <label>{{ ::'type' | translate }}</label>
-            <md-select ng-model='connectedComponent.type'
-                       ng-change='labelController.connectedComponentTypeChanged(connectedComponent)'
-                       style='width: 200px'>
-              <md-option value='importWork'>
-                {{ ::'importWork' | translate }}
-              </md-option>
-              <md-option value='showWork'>
-                {{ ::'showWork' | translate }}
-              </md-option>
-            </md-select>
-          </md-input-container>
-          <span flex></span>
-          <md-input-container style='margin-left: 20px;'>
-            <md-button class='topButton md-raised md-primary'
-                       ng-click='labelController.deleteConnectedComponent($index)'>
-              <md-icon>delete</md-icon>
-              <md-tooltip md-direction='top'
-                          class='projectButtonTooltip'>
-                {{ ::'DELETE' | translate }}
-              </md-tooltip>
-            </md-button>
-          </md-input-container>
-        </div>
-        <div ng-if='labelController.getConnectedComponentType(connectedComponent) == "OpenResponse"' flex>
-          <md-input-container style='margin-right: 20px;'>
-            <md-checkbox class='md-primary'
-                         ng-model='connectedComponent.importWorkAsBackground'
-                         ng-change='labelController.importWorkAsBackgroundClicked(connectedComponent)'>
-              {{ ::'importWorkAsBackground' | translate }}
-            </md-checkbox>
-          </md-input-container>
-          <div ng-if='connectedComponent.importWorkAsBackground'
-               style='display: inline;'>
-            <md-input-container style='margin-right: 20px; width: 200px; height: 30px;'>
-              <label>{{ ::'label.charactersPerLine' | translate }}</label>
-              <input ng-model='connectedComponent.charactersPerLine'
-                     ng-model-options='{ debounce: 1000 }'
-                     ng-change='labelController.componentChanged()'/>
-            </md-input-container>
-            <md-input-container style='margin-right: 20px width: 200px; height: 30px;'>
-              <label>{{ ::'label.spaceInbetweenLines' | translate }}</label>
-              <input ng-model='connectedComponent.spaceInbetweenLines'
-                     ng-model-options='{ debounce: 1000 }'
-                     ng-change='labelController.componentChanged()'/>
-            </md-input-container>
-            <md-input-container style='margin-right: 20px width: 100px; height: 30px;'>
-              <label>{{ ::'label.fontSize' | translate }}</label>
-              <input ng-model='connectedComponent.fontSize'
-                     ng-model-options='{ debounce: 1000 }'
-                     ng-change='labelController.componentChanged()'/>
-            </md-input-container>
-          </div>
-        </div>
-        <div ng-if='labelController.getConnectedComponentType(connectedComponent) == "ConceptMap" || labelController.getConnectedComponentType(connectedComponent) == "Draw" || labelController.getConnectedComponentType(connectedComponent) == "Embedded" || labelController.getConnectedComponentType(connectedComponent) == "Graph" || labelController.getConnectedComponentType(connectedComponent) == "Table"' flex>
-          <md-input-container style='margin-right: 20px;'>
-            <md-checkbox class='md-primary'
-                         ng-model='connectedComponent.importWorkAsBackground'
-                         ng-change='labelController.importWorkAsBackgroundClicked(connectedComponent)'
-                         ng-disabled='true'>
-              {{ ::'importWorkAsBackground' | translate }}
-            </md-checkbox>
-          </md-input-container>
-        </div>
-      </div>
-    </div>
-    <edit-component-json [node-id]="labelController.nodeId" [component-id]="labelController.componentId"></edit-component-json>
-  </div>
-  <br/>
-  <div>
-    <div ng-if='!labelController.authoringComponentContent.showPreviousWork'>
-      <md-input-container style='width:100%'>
-        <label>{{ ::'PROMPT' | translate }}</label>
-        <textarea rows='1'
-                  ng-model='labelController.authoringComponentContent.prompt'
-                  ng-change='labelController.componentChanged()'
-                  ng-model-options='{ debounce: 1000 }'
-                  placeholder='{{ ::"enterPromptHere" | translate }}'>
-      </textarea>
-      </md-input-container>
-      <div style='height: 60px;'>
-        <md-input-container>
-          <label>{{ ::'BACKGROUND_IMAGE' | translate }}</label>
-          <input size='100'
-                 ng-model='labelController.authoringComponentContent.backgroundImage'
-                 ng-change='labelController.componentChanged()'/>
-        </md-input-container>
-        <md-button class='topButton md-raised md-primary'
-                   ng-click='labelController.chooseBackgroundImage()'>
-          <md-icon>insert_photo</md-icon>
-          <md-tooltip md-direction='top'
-                      class='projectButtonTooltip'>
-            {{ ::'chooseAnImage' | translate }}
-          </md-tooltip>
-        </md-button>
-      </div>
-      <div style='height: 60px;'>
-        <md-input-container>
-          <label>{{ ::'label.canvasWidth' | translate }}</label>
-          <input type='number'
-                 style='width: 200px'
-                 ng-model='labelController.authoringComponentContent.width'
-                 ng-change='labelController.componentChanged()'/>
-        </md-input-container>
-        <md-input-container>
-          <label>{{ ::'label.canvasHeight' | translate }}</label>
-          <input type='number'
-                 style='width: 200px'
-                 ng-model='labelController.authoringComponentContent.height'
-                 ng-change='labelController.componentChanged()'/>
-        </md-input-container>
-      </div>
-      <div style='height: 60px;'>
-        <md-input-container>
-          <label>{{ ::'label.pointSize' | translate }}</label>
-          <input type='number'
-                 style='width: 200px'
-                 ng-model='labelController.authoringComponentContent.pointSize'
-                 ng-change='labelController.componentChanged()'/>
-        </md-input-container>
-        <md-input-container>
-          <label>{{ ::'label.fontSize' | translate }}</label>
-          <input type='number'
-                 style='width: 200px'
-                 ng-model='labelController.authoringComponentContent.fontSize'
-                 ng-change='labelController.componentChanged()'/>
-        </md-input-container>
-        <md-input-container>
-          <label>{{ ::'label.labelWidth' | translate }}</label>
-          <input type='number'
-                 style='width: 200px'
-                 ng-model='labelController.authoringComponentContent.labelWidth'
-                 ng-change='labelController.componentChanged()'/>
-        </md-input-container>
-      </div>
-      <div>
-        <md-checkbox class='md-primary'
-                     ng-model='labelController.authoringComponentContent.canCreateLabels'
-                     ng-change='labelController.componentChanged()'>
-          {{ ::'label.canStudentCreateLabels' | translate }}
-        </md-checkbox>
-        <br/>
-        <md-checkbox class='md-primary'
-                     ng-model='labelController.authoringComponentContent.enableCircles'
-                     ng-change='labelController.componentChanged()'>
-          {{ ::'label.enableDots' | translate }}
-        </md-checkbox>
-        <br/>
-        <md-checkbox class='md-primary'
-                     ng-model='labelController.authoringComponentContent.enableStudentUploadBackground'
-                     ng-change='labelController.componentChanged()'>
-          {{ ::'label.allowStudentToUploadImageForBackground' | translate }}
-        </md-checkbox>
-      </div>
-      <div>
-        <div layout='row'>
-          <h6>{{ ::'label.starterLabels' | translate }}</h6>
-          <md-button class='topButton md-raised md-primary'
-                     ng-click='labelController.addLabelClicked()'>
-            <md-icon>add</md-icon>
-            <md-tooltip md-direction='top'
-                        class='projectButtonTooltip'>
-              {{ ::'label.addStarterLabel' | translate }}
-            </md-tooltip>
-          </md-button>
-        </div>
-        <div ng-if='labelController.authoringComponentContent.labels == null || labelController.authoringComponentContent.labels.length == 0'>
-          {{ ::'label.thereAreNoStarterLabels' | translate }}
-        </div>
-        <div ng-repeat='label in labelController.authoringComponentContent.labels track by $index'
-             style='border: 2px solid #dddddd; border-radius: 5px; margin-bottom: 10px; padding: 20px 20px 10px 20px;'>
-          <div style='height: 60px;'>
-            <md-input-container style='margin-right: 20px;'>
-              <label>{{ ::'TEXT' | translate }}</label>
-              <input ng-model='label.text'
-                     ng-change='labelController.componentChanged()'
-                     ng-model-options='{ debounce: 1000 }'
-                     size='50'
-                     placeholder='{{ ::"label.enterLabelTextHere" | translate }}'/>
-            </md-input-container>
-            <md-input-container>
-              <label>{{ ::'label.color' | translate }}</label>
-              <input ng-model='label.color'
-                     ng-change='labelController.componentChanged()'
-                     ng-model-options='{ debounce: 1000 }'/>
-            </md-input-container>
-            <md-button class='topButton md-raised md-primary'
-                       ng-click='labelController.openColorViewer()'>
-              <md-icon>palette</md-icon>
-              <md-tooltip md-direction='top'
-                          class='projectButtonTooltip'>
-                {{ ::'label.viewColors' | translate }}
-              </md-tooltip>
-            </md-button>
-          </div>
-          <div style='height: 80px; margin-top: 10px;'>
-            <md-checkbox class='md-primary'
-                         ng-model='label.canEdit'
-                         ng-change='labelController.componentChanged()'>
-              {{ ::'label.canStudentEditLabel' | translate }}
-            </md-checkbox>
-            <br/>
-            <md-checkbox class='md-primary'
-                         ng-model='label.canDelete'
-                         ng-change='labelController.componentChanged()'>
-              {{ ::'label.canStudentDeleteLabel' | translate }}
-            </md-checkbox>
-          </div>
-          <div style='height: 60px;'
-               ng-if='labelController.enableCircles'>
-            <md-input-container style='margin-right: 20px;'>
-              <span>{{ ::'label.pointLocation' | translate }}</span>
-            </md-input-container>
-            <md-input-container style='margin-right: 20px;'>
-              <label>{{ ::'label.x' | translate }}</label>
-              <input type='number'
-                     ng-model='label.pointX'
-                     ng-change='labelController.componentChanged()'
-                     ng-model-options='{ debounce: 1000 }'/>
-            </md-input-container>
-            <md-input-container>
-              <label>{{ ::'label.y' | translate }}</label>
-              <input type='number'
-                     ng-model='label.pointY'
-                     ng-change='labelController.componentChanged()'
-                     ng-model-options='{ debounce: 1000 }'/>
-            </md-input-container>
-          </div>
-          <div style='height: 60px;'
-               layout='row'>
-            <md-input-container style='margin-right: 20px;'>
-              <span>{{ ::'label.textLocation' | translate }}</span>
-            </md-input-container>
-            <md-input-container style='margin-right: 20px;'>
-              <label>{{ ::'label.x' | translate }}</label>
-              <input type='number'
-                     ng-model='label.textX'
-                     ng-change='labelController.componentChanged()'
-                     ng-model-options='{ debounce: 1000 }'/>
-            </md-input-container>
-            <md-input-container>
-              <label>{{ ::'label.y' | translate }}</label>
-              <input type='number'
-                     ng-model='label.textY'
-                     ng-change='labelController.componentChanged()'
-                     ng-model-options='{ debounce: 1000 }'/>
-            </md-input-container>
-            <span flex></span>
-            <md-button class='moveComponentButton md-raised md-primary'
-                       style='margin-top: 20px;'
-                       ng-click='labelController.deleteLabelClicked($index, label)'>
-              <md-icon>delete</md-icon>
-              <md-tooltip md-direction='top'
-                          class='projectButtonTooltip'>
-                {{ ::'label.deleteLabel' | translate }}
-              </md-tooltip>
-            </md-button>
-          </div>
-        </div>
-      </div>
-      <md-button class='topButton md-raised md-primary'
-                 ng-click='labelController.saveStarterLabels()'>
-        <md-icon>create</md-icon>
-        <md-tooltip md-direction='top'
-                    class='projectButtonTooltip'>
-          {{ ::'label.saveStarterLabels' | translate }}
-        </md-tooltip>
-      </md-button>
-      <md-button class='topButton md-raised md-primary'
-                 ng-click='labelController.deleteStarterLabels()'>
-        <md-icon>delete_sweep</md-icon>
-        <md-tooltip md-direction='top'
-                    class='projectButtonTooltip'>
-          {{ ::'label.deleteStarterLabels' | translate }}
-        </md-tooltip>
-      </md-button>
-    </div>
-  </div>
-</div>
diff --git a/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.html b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.html
new file mode 100644
index 0000000000..9aa57edb6b
--- /dev/null
+++ b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.html
@@ -0,0 +1,236 @@
+<mat-form-field class="prompt">
+  <mat-label i18n>Prompt</mat-label>
+  <textarea matInput
+      [ngModel]="authoringComponentContent.prompt"
+      (ngModelChange)="promptChanged($event)"
+      placeholder="Enter Prompt Here"
+      i18n-placeholder
+      cdkTextareaAutosize>
+  </textarea>
+</mat-form-field>
+<div>
+  <mat-form-field class="background-image input">
+    <mat-label i18n>Background Image</mat-label>
+    <input matInput
+        [(ngModel)]="authoringComponentContent.backgroundImage"
+        (ngModelChange)="textInputChange.next($event)"/>
+  </mat-form-field>
+  <button mat-raised-button
+      color="primary"
+      (click)="chooseBackgroundImage()"
+      matTooltip="Choose an Image"
+      matTooltipPosition="above"
+      i18n-matTooltip
+      aria-label="Image"
+      i18n-aria-label>
+    <mat-icon>insert_photo</mat-icon>
+  </button>
+</div>
+<div>
+  <mat-form-field class="input">
+    <mat-label i18n>Canvas Width (px)</mat-label>
+    <input matInput
+        type="number"
+        [(ngModel)]="authoringComponentContent.width"
+        (ngModelChange)="numberInputChange.next($event)"/>
+  </mat-form-field>
+  <mat-form-field class="input">
+    <mat-label i18n>Canvas Height (px)</mat-label>
+    <input matInput
+        type="number"
+        [(ngModel)]="authoringComponentContent.height"
+        (ngModelChange)="numberInputChange.next($event)"/>
+  </mat-form-field>
+</div>
+<div>
+  <mat-form-field class="input">
+    <mat-label i18n>Point Radius Size (px)</mat-label>
+    <input matInput
+        type="number"
+        [(ngModel)]="authoringComponentContent.pointSize"
+        (ngModelChange)="numberInputChange.next($event)"/>
+  </mat-form-field>
+  <mat-form-field class="input">
+    <mat-label i18n>Font Size</mat-label>
+    <input matInput
+        type="number"
+        [(ngModel)]="authoringComponentContent.fontSize"
+        (ngModelChange)="numberInputChange.next($event)"/>
+  </mat-form-field>
+  <mat-form-field class="input">
+    <mat-label i18n>Label Max Character Width</mat-label>
+    <input matInput
+        type="number"
+        [(ngModel)]="authoringComponentContent.labelWidth"
+        (ngModelChange)="numberInputChange.next($event)"/>
+  </mat-form-field>
+</div>
+<div>
+  <div class="checkbox">
+    <mat-checkbox
+        color="primary"
+        [(ngModel)]="authoringComponentContent.canCreateLabels"
+        (change)="componentChanged()"
+        i18n>
+      Can Student Create Labels
+    </mat-checkbox>
+  </div>
+  <div class="checkbox">
+    <mat-checkbox
+        color="primary"
+        [(ngModel)]="authoringComponentContent.enableCircles"
+        (ngModelChange)="componentChanged()"
+        i18n>
+      Enable Dots
+    </mat-checkbox>
+  </div>
+  <div class="checkbox">
+    <mat-checkbox
+        color="primary"
+        [(ngModel)]="authoringComponentContent.enableStudentUploadBackground"
+        (ngModelChange)="componentChanged()"
+        i18n>
+      Allow Student to Upload Image for Background
+    </mat-checkbox>
+  </div>
+</div>
+<div>
+  <div layout="row" class="starter-labels-button-container">
+    <span i18n class="starter-labels-button-label">Starter Labels</span>
+    <button mat-raised-button
+        color="primary"
+        (click)="addLabel()"
+        matTooltip="Add Starter Label"
+        matTooltipPosition="above"
+        i18n-matTooltip
+        aria-label="add"
+        i18n-aria-label>
+      <mat-icon>add</mat-icon>
+    </button>
+  </div>
+  <div *ngIf="authoringComponentContent.labels == null || authoringComponentContent.labels.length === 0"
+      class="info-block"
+      i18n>
+    There are no starter labels. Click the "Add Label" button to add a starter label.
+  </div>
+  <div *ngFor="let label of authoringComponentContent.labels; index as labelIndex"
+      class="starter-label-container">
+    <div>
+      <mat-form-field class="input label-input">
+        <mat-label i18n>Text</mat-label>
+        <input matInput
+            [(ngModel)]="label.text"
+            (ngModelChange)="textInputChange.next($event)"
+            placeholder="Enter Label Text Here"
+            i18n-placeholder/>
+      </mat-form-field>
+      <mat-form-field class="input color-input">
+        <mat-label i18n>Color</mat-label>
+        <input matInput
+            [(ngModel)]="label.color"
+            (ngModelChange)="textInputChange.next($event)"
+            ng-model-options="{ debounce: 1000 }"/>
+      </mat-form-field>
+      <button mat-raised-button
+          color="primary"
+          (click)="openColorViewer()"
+          matTooltip="View Colors"
+          matTooltipPosition="above"
+          i18n-matTooltip
+          aria-label="Color Palette"
+          i18n-aria-label>
+        <mat-icon>palette</mat-icon>
+      </button>
+    </div>
+    <div>
+      <div class="checkbox">
+      <mat-checkbox
+          color="primary"
+          [(ngModel)]="label.canEdit"
+          (ngModelChange)="componentChanged()"
+          i18n>
+        Can Student Edit Label
+      </mat-checkbox>
+      </div>
+      <div class="checkbox">
+      <mat-checkbox
+          color="primary"
+          [(ngModel)]="label.canDelete"
+          (ngModelChange)="componentChanged()"
+          i18n>
+        Can Student Delete Label
+      </mat-checkbox>
+      </div>
+    </div>
+    <div *ngIf="authoringComponentContent.enableCircles"
+        fxLayout="row wrap" fxLayoutAlign="start center">
+      <span class="coordinate-location-label" i18n>Point Location</span>
+      <mat-form-field class="input coordinate-input">
+        <mat-label i18n>X</mat-label>
+        <input matInput
+            type="number"
+            [(ngModel)]="label.pointX"
+            (ngModelChange)="numberInputChange.next($event)"/>
+      </mat-form-field>
+      <mat-form-field class="input coordinate-input">
+        <mat-label i18n>Y</mat-label>
+        <input matInput
+            type="number"
+            [(ngModel)]="label.pointY"
+            (ngModelChange)="numberInputChange.next($event)"/>
+      </mat-form-field>
+    </div>
+    <div fxLayout="row wrap" fxLayoutAlign="start center">
+      <span class="coordinate-location-label" i18n>Text Location</span>
+      <mat-form-field class="input coordinate-input">
+        <mat-label i18n>X</mat-label>
+        <input matInput
+            type="number"
+            [(ngModel)]="label.textX"
+            (ngModelChange)="numberInputChange.next($event)"/>
+      </mat-form-field>
+      <mat-form-field class="input coordinate-input">
+        <mat-label i18n>Y</mat-label>
+        <input matInput
+            type="number"
+            [(ngModel)]="label.textY"
+            (ngModelChange)="numberInputChange.next($event)"/>
+      </mat-form-field>
+      <span fxFlex></span>
+      <button mat-raised-button
+          color="primary"
+          (click)="deleteLabel(labelIndex, label)"
+          matTooltip="Delete Label"
+          matTooltipPosition="above"
+          i18n-matTooltip
+          aria-label="Delete"
+          i18n-aria-label>
+        <mat-icon>delete</mat-icon>
+      </button>
+    </div>
+  </div>
+</div>
+<div class="starter-labels-buttons-container">
+  <button mat-raised-button
+      color="primary"
+      class="starter-labels-button"
+      (click)="saveStarterLabels()"
+      matTooltip="Save Starter Labels"
+      matTooltipPosition="above"
+      i18n-matTooltip
+      aria-label="Save"
+      i18n-aria-label>
+    <mat-icon>create</mat-icon>
+  </button>
+  <button mat-raised-button
+      color="primary"
+      class="starter-labels-button"
+      (click)="deleteStarterLabels()"
+      matTooltip="Delete Starter Labels"
+      matTooltipPosition="above"
+      i18n-matTooltip
+      aria-label="Delete"
+      i18n-aria-label>
+    <mat-icon>delete_sweep</mat-icon>
+  </button>
+</div>
diff --git a/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.scss b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.scss
new file mode 100644
index 0000000000..2115fcb9d5
--- /dev/null
+++ b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.scss
@@ -0,0 +1,64 @@
+.prompt {
+  width: 100%;
+}
+
+.background-image {
+  width: 80%;
+}
+
+.input {
+  margin-right: 20px;
+}
+
+.checkbox {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+
+.starter-labels-button-container {
+  margin-top: 20px;
+  margin-bottom: 20px;
+}
+
+.starter-labels-button-label {
+  margin-right: 10px;
+}
+
+.starter-label-container {
+  border: 2px solid #dddddd;
+  border-radius: 5px;
+  margin-bottom: 10px;
+  padding: 20px 20px 10px 20px;
+}
+
+.info-block {
+  margin-bottom: 20px;
+  text-align: center;
+  font-weight: 500;
+}
+
+.label-input {
+  width: 40%;
+}
+
+.color-input {
+  width: 30%;
+}
+
+.coordinate-location-label {
+  margin-right: 20px;
+}
+
+.coordinate-input {
+  width: 15%;
+}
+
+.starter-labels-buttons-container {
+  margin-top: 20px;
+  margin-bottom: 10px;
+  margin-left: 10px;
+}
+
+.starter-labels-button {
+  margin-right: 10px;
+}
\ No newline at end of file
diff --git a/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.ts b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.ts
new file mode 100644
index 0000000000..2c2ea4ff2b
--- /dev/null
+++ b/src/main/webapp/wise5/components/label/label-authoring/label-authoring.component.ts
@@ -0,0 +1,124 @@
+'use strict';
+
+import { Component } from '@angular/core';
+import { ComponentAuthoring } from '../../../authoringTool/components/component-authoring.component';
+import { ConfigService } from '../../../services/configService';
+import { NodeService } from '../../../services/nodeService';
+import { ProjectAssetService } from '../../../../site/src/app/services/projectAssetService';
+import { TeacherProjectService } from '../../../services/teacherProjectService';
+import { Subject, Subscription } from 'rxjs';
+import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
+
+@Component({
+  selector: 'label-authoring',
+  templateUrl: 'label-authoring.component.html',
+  styleUrls: ['label-authoring.component.scss']
+})
+export class LabelAuthoring extends ComponentAuthoring {
+  numberInputChange: Subject<number> = new Subject<number>();
+  textInputChange: Subject<string> = new Subject<string>();
+
+  numberInputChangeSubscription: Subscription;
+  textInputChangeSubscription: Subscription;
+
+  constructor(
+    protected ConfigService: ConfigService,
+    protected NodeService: NodeService,
+    protected ProjectAssetService: ProjectAssetService,
+    protected ProjectService: TeacherProjectService
+  ) {
+    super(ConfigService, NodeService, ProjectAssetService, ProjectService);
+    this.numberInputChangeSubscription = this.numberInputChange
+      .pipe(debounceTime(1000), distinctUntilChanged())
+      .subscribe(() => {
+        this.componentChanged();
+      });
+    this.textInputChangeSubscription = this.textInputChange
+      .pipe(debounceTime(1000), distinctUntilChanged())
+      .subscribe(() => {
+        this.componentChanged();
+      });
+  }
+
+  ngOnInit() {
+    super.ngOnInit();
+    if (this.authoringComponentContent.enableCircles == null) {
+      // If this component was created before enableCircles was implemented, we will default it to
+      // true in the authoring so that the "Enable Dots" checkbox is checked.
+      this.authoringComponentContent.enableCircles = true;
+    }
+  }
+
+  ngOnDestroy() {
+    super.ngOnDestroy();
+    this.unsubscribeAll();
+  }
+
+  unsubscribeAll() {
+    this.numberInputChangeSubscription.unsubscribe();
+    this.textInputChangeSubscription.unsubscribe();
+  }
+
+  addLabel(): void {
+    const newLabel = {
+      text: $localize`Enter text here`,
+      color: 'blue',
+      pointX: 100,
+      pointY: 100,
+      textX: 200,
+      textY: 200,
+      canEdit: false,
+      canDelete: false
+    };
+    this.authoringComponentContent.labels.push(newLabel);
+    this.componentChanged();
+  }
+
+  deleteLabel(index: number, label: any): void {
+    if (confirm($localize`Are you sure you want to delete this label?\n\n${label.text}`)) {
+      this.authoringComponentContent.labels.splice(index, 1);
+      this.componentChanged();
+    }
+  }
+
+  assetSelected({ nodeId, componentId, assetItem, target }): void {
+    super.assetSelected({ nodeId, componentId, assetItem, target });
+    const fileName = assetItem.fileName;
+    if (target === 'background') {
+      this.authoringComponentContent.backgroundImage = fileName;
+      this.componentChanged();
+    }
+  }
+
+  saveStarterLabels(): void {
+    if (confirm($localize`Are you sure you want to save the starter labels?`)) {
+      this.NodeService.requestStarterState({ nodeId: this.nodeId, componentId: this.componentId });
+    }
+  }
+
+  saveStarterState(starterState: any): void {
+    this.authoringComponentContent.labels = starterState;
+    this.componentChanged();
+  }
+
+  compareTextAlphabetically(stringA: string, stringB: string) {
+    if (stringA < stringB) {
+      return -1;
+    } else if (stringA > stringB) {
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+
+  deleteStarterLabels(): void {
+    if (confirm($localize`label.areYouSureYouWantToDeleteAllTheStarterLabels`)) {
+      this.authoringComponentContent.labels = [];
+      this.componentChanged();
+    }
+  }
+
+  openColorViewer(): void {
+    window.open('http://www.javascripter.net/faq/colornam.htm');
+  }
+}
diff --git a/src/main/webapp/wise5/components/label/labelAuthoring.ts b/src/main/webapp/wise5/components/label/labelAuthoring.ts
deleted file mode 100644
index df29a8ac22..0000000000
--- a/src/main/webapp/wise5/components/label/labelAuthoring.ts
+++ /dev/null
@@ -1,177 +0,0 @@
-'use strict';
-
-import * as $ from 'jquery';
-import * as fabric from 'fabric';
-window['fabric'] = fabric.fabric;
-import html2canvas from 'html2canvas';
-import { Directive } from '@angular/core';
-import { EditComponentController } from '../../authoringTool/components/editComponentController';
-
-@Directive()
-class LabelAuthoringController extends EditComponentController {
-  static $inject = [
-    '$filter',
-    '$window',
-    'ConfigService',
-    'NodeService',
-    'NotificationService',
-    'ProjectAssetService',
-    'ProjectService',
-    'UtilService'
-  ];
-
-  constructor(
-    $filter,
-    private $window,
-    ConfigService,
-    NodeService,
-    NotificationService,
-    ProjectAssetService,
-    ProjectService,
-    UtilService
-  ) {
-    super(
-      $filter,
-      ConfigService,
-      NodeService,
-      NotificationService,
-      ProjectAssetService,
-      ProjectService,
-      UtilService
-    );
-  }
-
-  $onInit() {
-    super.$onInit();
-    if (this.authoringComponentContent.enableCircles == null) {
-      /*
-       * If this component was created before enableCircles was implemented,
-       * we will default it to true in the authoring so that the
-       * "Enable Dots" checkbox is checked.
-       */
-      this.authoringComponentContent.enableCircles = true;
-    }
-  }
-
-  addLabelClicked(): void {
-    const newLabel = {
-      text: this.$translate('label.enterTextHere'),
-      color: 'blue',
-      pointX: 100,
-      pointY: 100,
-      textX: 200,
-      textY: 200,
-      canEdit: false,
-      canDelete: false
-    };
-    this.authoringComponentContent.labels.push(newLabel);
-    this.componentChanged();
-  }
-
-  /**
-   * Delete a label in the authoring view
-   * @param index the index of the label in the labels array
-   */
-  deleteLabelClicked(index: number, label: any): void {
-    const answer = confirm(
-      this.$translate('label.areYouSureYouWantToDeleteThisLabel', {
-        selectedLabelText: label.textString
-      })
-    );
-    if (answer) {
-      this.authoringComponentContent.labels.splice(index, 1);
-      this.componentChanged();
-    }
-  }
-
-  chooseBackgroundImage(): void {
-    const params = {
-      isPopup: true,
-      nodeId: this.nodeId,
-      componentId: this.componentId,
-      target: 'background'
-    };
-    this.openAssetChooser(params);
-  }
-
-  assetSelected({ nodeId, componentId, assetItem, target }): void {
-    super.assetSelected({ nodeId, componentId, assetItem, target });
-    const fileName = assetItem.fileName;
-    if (target === 'background') {
-      this.authoringComponentContent.backgroundImage = fileName;
-      this.componentChanged();
-    }
-  }
-
-  saveStarterLabels(): void {
-    if (confirm(this.$translate('label.areYouSureYouWantToSaveTheStarterLabels'))) {
-      this.NodeService.requestStarterState({ nodeId: this.nodeId, componentId: this.componentId });
-    }
-  }
-
-  saveStarterState(starterState: any): void {
-    starterState.sort(this.labelTextComparator);
-    this.authoringComponentContent.labels = starterState;
-    this.componentChanged();
-  }
-
-  /**
-   * A comparator used to sort labels alphabetically
-   * It should be used like labels.sort(this.labelTextComparator);
-   * @param labelA a label object
-   * @param labelB a label object
-   * @return -1 if labelA comes before labelB
-   * 1 if labelB comes after labelB
-   * 0 of the labels are equal
-   */
-  labelTextComparator(labelA: any, labelB: any): number {
-    if (labelA.text < labelB.text) {
-      return -1;
-    } else if (labelA.text > labelB.text) {
-      return 1;
-    } else {
-      if (labelA.color < labelB.color) {
-        return -1;
-      } else if (labelA.color > labelB.color) {
-        return 1;
-      } else {
-        if (labelA.pointX < labelB.pointX) {
-          return -1;
-        } else if (labelA.pointX > labelB.pointX) {
-          return 1;
-        } else {
-          if (labelA.pointY < labelB.pointY) {
-            return -1;
-          } else if (labelA.pointY > labelB.pointY) {
-            return 1;
-          } else {
-            return 0;
-          }
-        }
-      }
-    }
-  }
-
-  deleteStarterLabels(): void {
-    if (confirm(this.$translate('label.areYouSureYouWantToDeleteAllTheStarterLabels'))) {
-      this.authoringComponentContent.labels = [];
-      this.componentChanged();
-    }
-  }
-
-  openColorViewer(): void {
-    this.$window.open('http://www.javascripter.net/faq/colornam.htm');
-  }
-}
-
-const LabelAuthoring = {
-  bindings: {
-    nodeId: '@',
-    componentId: '@'
-  },
-  controller: LabelAuthoringController,
-  controllerAs: 'labelController',
-  templateUrl: 'wise5/components/label/authoring.html'
-};
-
-export default LabelAuthoring;
diff --git a/src/main/webapp/wise5/components/label/labelAuthoringComponentModule.ts b/src/main/webapp/wise5/components/label/labelAuthoringComponentModule.ts
index cceefafef9..e6bd14a03c 100644
--- a/src/main/webapp/wise5/components/label/labelAuthoringComponentModule.ts
+++ b/src/main/webapp/wise5/components/label/labelAuthoringComponentModule.ts
@@ -1,15 +1,18 @@
 'use strict';
 
 import * as angular from 'angular';
-import { downgradeInjectable } from '@angular/upgrade/static';
+import { downgradeComponent, downgradeInjectable } from '@angular/upgrade/static';
 import { LabelService } from './labelService';
-import LabelAuthoring from './labelAuthoring';
 import { EditLabelAdvancedComponent } from './edit-label-advanced/edit-label-advanced.component';
+import { LabelAuthoring } from './label-authoring/label-authoring.component';
 
 const labelAuthoringComponentModule = angular
   .module('labelAuthoringComponentModule', ['pascalprecht.translate'])
   .service('LabelService', downgradeInjectable(LabelService))
-  .component('labelAuthoring', LabelAuthoring)
+  .directive(
+    'labelAuthoring',
+    downgradeComponent({ component: LabelAuthoring }) as angular.IDirectiveFactory
+  )
   .component('editLabelAdvanced', EditLabelAdvancedComponent)
   .config([
     '$translatePartialLoaderProvider',
diff --git a/src/main/webapp/wise5/components/table/authoring.html b/src/main/webapp/wise5/components/table/authoring.html
deleted file mode 100644
index 08bbdb80d3..0000000000
--- a/src/main/webapp/wise5/components/table/authoring.html
+++ /dev/null
@@ -1,381 +0,0 @@
-<div style='display: flex'>
-  <style>
-    .rotate90 {
-      -webkit-transform: rotate(90deg);
-      transform: rotate(90deg);
-    }
-    .rotate270 {
-      -webkit-transform: rotate(270deg);
-      transform: rotate(270deg);
-    }
-  </style>
-  <div style='flex: 1'>
-    <div>
-      <div class='advancedAuthoringDiv'
-           ng-if='tableController.showAdvancedAuthoring'>
-        <div>
-          <md-checkbox class='md-primary'
-                       ng-model='tableController.authoringComponentContent.showSaveButton'
-                       ng-change='tableController.componentChanged()'>
-            {{ ::'SHOW_SAVE_BUTTON' | translate }}
-          </md-checkbox>
-        </div>
-        <div>
-          <md-checkbox class='md-primary'
-                       ng-model='tableController.authoringComponentContent.showSubmitButton'
-                       ng-change='tableController.componentChanged()'>
-            {{ ::'SHOW_SUBMIT_BUTTON' | translate }}
-          </md-checkbox>
-        </div>
-        <div>
-          <md-checkbox class='md-primary'
-                       ng-if='tableController.isNotebookEnabled()'
-                       ng-model='tableController.authoringComponentContent.showAddToNotebookButton'
-                       ng-change='tableController.componentChanged()'>
-            {{ ::'SHOW_ADD_TO_NOTEBOOK_BUTTON' | translate }}
-          </md-checkbox>
-        </div>
-        <div>
-          <md-input-container style='margin-right: 20px; width: 150px; height: 25px;'
-                              ng-if='tableController.authoringComponentContent.showSubmitButton'>
-            <label>{{ ::'MAX_SUBMIT' | translate }}</label>
-            <input type='number'
-                   ng-model='tableController.authoringComponentContent.maxSubmitCount'
-                   ng-model-options='{ debounce: 1000 }'
-                   ng-change='tableController.componentChanged()'/>
-          </md-input-container>
-        </div>
-        <div style='border: 1px solid black'>
-          <p>{{ ::'table.dataExplorer' | translate }}</p>
-          <div>
-            <md-checkbox class='md-primary'
-                ng-model='tableController.authoringComponentContent.isDataExplorerEnabled'
-                ng-change='tableController.toggleDataExplorer()'>
-              {{ ::'table.enableDataExplorer' | translate }}
-            </md-checkbox>
-          </div>
-          <div ng-if='tableController.authoringComponentContent.isDataExplorerEnabled'>
-            <div>
-              <p>{{ ::'table.allowedGraphTypes' | translate }}</p>
-              <md-checkbox class='md-primary'
-                  style='margin-right: 40px;'
-                  ng-model='tableController.isDataExplorerScatterPlotEnabled'
-                  ng-change='tableController.dataExplorerToggleScatterPlot()'>
-                {{ ::'table.scatterPlot' | translate }}
-              </md-checkbox>
-              <md-checkbox class='md-primary'
-                  style='margin-right: 40px;'
-                  ng-model='tableController.isDataExplorerLineGraphEnabled'
-                  ng-change='tableController.dataExplorerToggleLineGraph()'>
-                {{ ::'table.lineGraph' | translate }}
-              </md-checkbox>
-              <md-checkbox class='md-primary'
-                  ng-model='tableController.isDataExplorerBarGraphEnabled'
-                  ng-change='tableController.dataExplorerToggleBarGraph()'>
-                {{ ::'table.barGraph' | translate }}
-              </md-checkbox>
-            </div>
-            <div ng-if='tableController.isDataExplorerScatterPlotEnabled'>
-              <md-checkbox class='md-primary'
-                  ng-model='tableController.authoringComponentContent.isDataExplorerScatterPlotRegressionLineEnabled'
-                  ng-change='tableController.componentChanged()'>
-                {{ ::'table.showScatterPlotRegressionLine' | translate }}
-              </md-checkbox>
-            </div>
-            <div>
-              <md-input-container style='margin-right: 20px; width: 150px; height: 25px;'>
-                <label>{{ ::'table.numberOfSeries' | translate }}</label>
-                <input type='number'
-                    ng-model='tableController.authoringComponentContent.numDataExplorerSeries'
-                    ng-model-options='{ debounce: 1000 }'
-                    ng-change='tableController.numDataExplorerSeriesChanged()'/>
-              </md-input-container>
-            </div>
-            <div>
-              <md-input-container style='margin-right: 20px; width: 150px; height: 25px;'>
-                <label>{{ ::'table.numberOfYAxes' | translate }}</label>
-                <input type='number'
-                    ng-model='tableController.authoringComponentContent.numDataExplorerYAxis'
-                    ng-model-options='{ debounce: 1000 }'
-                    ng-change='tableController.numDataExplorerYAxisChanged()'/>
-              </md-input-container>
-            </div>
-            <div ng-if='tableController.authoringComponentContent.numDataExplorerYAxis > 1'>
-              <div ng-repeat='s in [].constructor(tableController.authoringComponentContent.numDataExplorerSeries) track by $index'>
-                <md-input-container style='margin-right: 20px; width: 300px;'>
-                  <label>{{ ::'table.series' | translate }} {{ $index + 1 }}</label>
-                  <md-select ng-model='tableController.authoringComponentContent.dataExplorerSeriesParams[$index].yAxis'
-                      ng-change='tableController.componentChanged()'
-                      style='width: 300px'>
-                    <md-option ng-repeat='y in [].constructor(tableController.authoringComponentContent.numDataExplorerYAxis) track by $index'
-                        ng-value='$index'>
-                      <span>
-                        {{ ::'table.yAxis' | translate }} {{ $index + 1 }}
-                      </span>
-                    </md-option>
-                  </md-select>
-                </md-input-container>
-              </div>
-            </div>
-            <div>
-              <md-checkbox class='md-primary'
-                  style='margin-right: 40px;'
-                  ng-model='tableController.authoringComponentContent.isDataExplorerAxisLabelsEditable'
-                  ng-change='tableController.componentChanged()'>
-                {{ ::'table.canStudentEditAxisLabels' | translate }}
-              </md-checkbox>
-            </div>
-          </div>
-        </div>
-        <div layout="column" layout-align="start start">
-          <edit-component-max-score [authoring-component-content]="tableController.authoringComponentContent"></edit-component-max-score>
-          <edit-component-width [authoring-component-content]="tableController.authoringComponentContent"></edit-component-width>
-          <edit-component-rubric [authoring-component-content]="tableController.authoringComponentContent"></edit-component-rubric>
-          <edit-component-tags [authoring-component-content]="tableController.authoringComponentContent"></edit-component-tags>
-        </div>
-        <div>
-          <div style='height: 50;'>
-            <label class='node__label--vertical-alignment'>
-              {{ ::'CONNECTED_COMPONENTS' | translate }}
-            </label>
-            <md-button class='topButton md-raised md-primary'
-                       ng-click='tableController.addConnectedComponent()'>
-              <md-icon>add</md-icon>
-              <md-tooltip md-direction='top'
-                          class='projectButtonTooltip'>
-                {{ ::'ADD_CONNECTED_COMPONENT' | translate }}
-              </md-tooltip>
-            </md-button>
-          </div>
-          <div ng-repeat='connectedComponent in tableController.authoringComponentContent.connectedComponents track by $index'
-               style='border: 2px solid #dddddd; border-radius: 5px; margin-bottom: 10px; padding: 20px 20px 10px 20px;'>
-            <div flex>
-              <md-input-container style='margin-right: 20px; width: 300px;'>
-                <label>{{ ::'step' | translate }}</label>
-                <md-select ng-model='connectedComponent.nodeId'
-                           ng-change='tableController.connectedComponentNodeIdChanged(connectedComponent)'
-                           style='width: 300px'>
-                  <md-option ng-repeat='item in tableController.idToOrder | toArray | orderBy : "order"'
-                             value='{{item.$key}}'
-                             ng-if='tableController.isApplicationNode(item.$key)'>
-                    {{tableController.getNodePositionAndTitleByNodeId(item.$key)}}
-                  </md-option>
-                </md-select>
-              </md-input-container>
-              <md-input-container style='margin-right: 20px; width: 300px;'>
-                <label>{{ ::'component' | translate }}</label>
-                <md-select ng-model='connectedComponent.componentId'
-                           ng-change='tableController.connectedComponentComponentIdChanged(connectedComponent)'
-                           style='width: 300px'>
-                  <md-option ng-repeat='(componentIndex, component) in tableController.getComponentsByNodeId(connectedComponent.nodeId)'
-                             value='{{component.id}}'
-                             ng-disabled='!tableController.isConnectedComponentTypeAllowed(component.type) || component.id == tableController.componentId'>
-                    {{ componentIndex + 1 }}. {{component.type}}
-                    <span ng-if='component.id == tableController.componentId'>
-                      ({{ ::'thisComponent' | translate }})
-                    </span>
-                  </md-option>
-                </md-select>
-              </md-input-container>
-              <md-input-container style='margin-right: 20px; width: 200px;'>
-                <label>{{ ::'type' | translate }}</label>
-                <md-select ng-model='connectedComponent.type'
-                           ng-change='tableController.connectedComponentTypeChanged(connectedComponent)'
-                           style='width: 200px'>
-                  <md-option value='importWork'>
-                    {{ ::'importWork' | translate }}
-                  </md-option>
-                  <md-option value='showWork'>
-                    {{ ::'showWork' | translate }}
-                  </md-option>
-                </md-select>
-              </md-input-container>
-              <md-input-container style='margin-right: 20px; width: 200px;'
-                                  ng-if='connectedComponent.type == "importWork"'>
-                <label>{{ ::'action' | translate }}</label>
-                <md-select ng-model='connectedComponent.action'
-                           ng-change='tableController.componentChanged()'
-                           style='width: 200px'>
-                  <md-option value='merge'>
-                    {{ ::'merge' | translate }}
-                  </md-option>
-                  <md-option value='append'>
-                    {{ ::'append' | translate }}
-                  </md-option>
-                </md-select>
-              </md-input-container>
-              <span flex></span>
-              <md-input-container style='margin-left: 20px;'>
-                <md-button class='topButton md-raised md-primary'
-                           ng-click='tableController.deleteConnectedComponent($index)'>
-                  <md-icon>delete</md-icon>
-                  <md-tooltip md-direction='top'
-                              class='projectButtonTooltip'>
-                    {{ ::'DELETE' | translate }}
-                  </md-tooltip>
-                </md-button>
-              </md-input-container>
-              <md-input-container ng-if='tableController.getConnectedComponentType(connectedComponent) == "Graph"'>
-                <md-checkbox class='md-primary'
-                             ng-model='connectedComponent.showDataAtMouseX'
-                             ng-change='tableController.componentChanged()'>
-                  {{ ::'table.onlyShowDataAtMouseXPosition' | translate }}
-                </md-checkbox>
-              </md-input-container>
-            </div>
-          </div>
-        </div>
-        <edit-component-json [node-id]="tableController.nodeId" [component-id]="tableController.componentId"></edit-component-json>
-      </div>
-      <br/>
-      <div ng-if='!tableController.authoringComponentContent.showPreviousWork'>
-        <md-input-container style='width:100%'>
-          <label>{{ ::'PROMPT' | translate }}</label>
-          <textarea rows='1'
-                    ng-model='tableController.authoringComponentContent.prompt'
-                    ng-change='tableController.componentChanged()'
-                    ng-model-options='{ debounce: 1000 }'
-                    aria-label='Prompt'
-                    placeholder='{{ "enterPromptHere" | translate }}'>
-        </textarea>
-        </md-input-container>
-        <br/>
-        <div style='height: 60px;'>
-          <md-input-container>
-            <label>{{ ::'table.columns' | translate }}</label>
-            <input type='number'
-                   style='width: 120px'
-                   ng-model='tableController.authoringComponentContent.numColumns'
-                   ng-model-options='{ debounce: 500 }'
-                   ng-change='tableController.tableNumColumnsChanged({{tableController.authoringComponentContent.numColumns}})'/>
-          </md-input-container>
-          <md-input-container>
-            <label>{{ ::'table.rows' | translate }}</label>
-            <input type='number'
-                   style='width: 120px'
-                   ng-model-options='{ debounce: 500 }'
-                   ng-model='tableController.authoringComponentContent.numRows'
-                   ng-change='tableController.tableNumRowsChanged({{tableController.authoringComponentContent.numRows}})'/>
-          </md-input-container>
-          <md-input-container>
-            <label>{{ ::'table.globalCellSize' | translate }}</label>
-            <input type='number'
-                   style='width: 120px'
-                   ng-model='tableController.authoringComponentContent.globalCellSize'
-                   ng-change='tableController.componentChanged()'/>
-          </md-input-container>
-        </div>
-        <br/>
-        <table style='border: 1px solid black;'>
-          <tr>
-            <td style='border: 1px solid black;'></td>
-            <td ng-repeat='x in [].constructor(tableController.authoringComponentContent.numColumns) track by $index'
-                style='border: 1px solid black;'>
-              <md-button class='topButton md-raised md-primary'
-                         ng-click='tableController.insertColumn($index)'>
-                <md-icon class='rotate90'>loupe</md-icon>
-                <md-tooltip md-direction='top'
-                            class='projectButtonTooltip'>
-                  {{ ::'table.insertColumnBefore' | translate }}
-                </md-tooltip>
-              </md-button>
-              <md-button class='topButton md-raised md-primary'
-                         ng-click='tableController.deleteColumn($index)'>
-                <md-icon>delete</md-icon>
-                <md-tooltip md-direction='top'
-                            class='projectButtonTooltip'>
-                  {{ ::'table.deleteColumn' | translate }}
-                </md-tooltip>
-              </md-button>
-              <md-button class='topButton md-raised md-primary'
-                         ng-if='$last'
-                         ng-click='tableController.insertColumn($index + 1)'>
-                <md-icon>loupe</md-icon>
-                <md-tooltip md-direction='top'
-                            class='projectButtonTooltip'>
-                  {{ ::'table.insertColumnAfter' | translate }}
-                </md-tooltip>
-              </md-button>
-            </td>
-          </tr>
-          <tr ng-repeat='row in tableController.authoringComponentContent.tableData'>
-            <td style='border: 1px solid black;'>
-              <md-button class='topButton md-raised md-primary'
-                         ng-click='tableController.insertRow($index)'>
-                <md-icon class='rotate270'>loupe</md-icon>
-                <md-tooltip md-direction='top'
-                            class='projectButtonTooltip'>
-                  {{ ::'table.insertRowBefore' | translate }}
-                </md-tooltip>
-              </md-button>
-              <br/>
-              <md-button class='topButton md-raised md-primary'
-                         ng-click='tableController.deleteRow($index)'>
-                <md-icon>delete</md-icon>
-                <md-tooltip md-direction='top'
-                            class='projectButtonTooltip'>
-                  {{ ::'table.deleteRow' | translate }}
-                </md-tooltip>
-              </md-button>
-              <br/>
-              <md-button class='topButton md-raised md-primary'
-                         ng-if='$last'
-                         ng-click='tableController.insertRow($index + 1)'>
-                <md-icon>loupe</md-icon>
-                <md-tooltip md-direction='top'
-                            class='projectButtonTooltip'>
-                  {{ ::'table.insertRowAfter' | translate }}
-                </md-tooltip>
-              </md-button>
-            </td>
-            <td ng-repeat='cell in row' style='border: 1px solid black; height: 20;'>
-              <md-input-container style='height: 20px;'>
-                <input ng-model='cell.text'
-                       ng-model-options='{ debounce: 1000 }'
-                       ng-change='tableController.componentChanged()'
-                       aria-label='Text'
-                       size='20'/>
-              </md-input-container>
-              <br/>
-              <md-checkbox class='md-primary'
-                           style='margin-left: 10px;'
-                           ng-model='cell.editable'
-                           ng-change='tableController.componentChanged()'>
-                {{ ::'table.editableByStudent' | translate }}
-              </md-checkbox>
-            </td>
-          </tr>
-          <tr>
-            <td></td>
-            <td ng-repeat='x in [].constructor(tableController.authoringComponentContent.numColumns) track by $index'>
-              &nbsp
-            </td>
-          </tr>
-          <tr>
-            <td style='border: 1px solid black; height: 20px;'>
-              <label style='padding: 5px;'>{{ ::'OPTIONAL' | translate }}</label>
-            </td>
-            <td ng-repeat='x in [].constructor(tableController.authoringComponentContent.numColumns) track by $index' style='border: 1px solid black; height: 20px;'>
-              <md-input-container style='height: 20px;'>
-                <label>{{ ::'table.columnCellSizes' | translate }}</label>
-                <input type='number'
-                       ng-model='tableController.columnCellSizes[$index]'
-                       ng-change='tableController.columnSizeChanged($index)'/>
-              </md-input-container>
-            </td>
-          </tr>
-        </table>
-        <br/>
-        <div layout='row'>
-          <md-button class='topButton md-raised md-primary'
-                     ng-click='tableController.makeAllCellsEditable()'>
-            {{ ::'table.makeAllCellsEditable' | translate }}
-          </md-button>
-          <md-button class='topButton md-raised md-primary'
-                     ng-click='tableController.makeAllCellsUneditable()'>
-            {{ ::'table.makeAllCellsUneditable' | translate }}
-          </md-button>
-        </div>
-      </div>
-    </div>
-  </div>
diff --git a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.html b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.html
new file mode 100644
index 0000000000..9fcbfba3af
--- /dev/null
+++ b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.html
@@ -0,0 +1,181 @@
+<mat-form-field class="prompt">
+  <mat-label i18n>Prompt</mat-label>
+  <textarea matInput
+      [ngModel]="authoringComponentContent.prompt"
+      (ngModelChange)="promptChanged($event)"
+      placeholder="Enter Prompt Here"
+      i18n-placeholder
+      cdkTextareaAutosize>
+  </textarea>
+</mat-form-field>
+<div>
+  <mat-form-field class="size-input">
+    <mat-label i18n>Columns</mat-label>
+    <input matInput
+        type="number"
+        min="1"
+        [(ngModel)]="authoringComponentContent.numColumns"
+        (ngModelChange)="numColumnsChange.next($event)">
+  </mat-form-field>
+  <mat-form-field class="size-input">
+    <mat-label i18n>Rows</mat-label>
+    <input matInput
+        type="number"
+        min="1"
+        [(ngModel)]="authoringComponentContent.numRows"
+        (ngModelChange)="numRowsChange.next($event)"/>
+  </mat-form-field>
+  <mat-form-field class="size-input">
+    <mat-label i18n>Global Cell Size</mat-label>
+    <input matInput
+        type="number"
+        min="1"
+        [(ngModel)]="authoringComponentContent.globalCellSize"
+        (ngModelChange)="globalCellSizeChange.next($event)"/>
+  </mat-form-field>
+</div>
+<table class="table">
+  <tr>
+    <td></td>
+    <td *ngFor="let x of [].constructor(getNumColumnsInTableData()); index as columnIndex; last as isLast"
+        class="outer-cell-container">
+      <div fxLayout="row wrap" fxLayoutAlign="space-between center">
+        <button mat-raised-button
+            color="primary"
+            class="column-buttons"
+            (click)="insertColumn(columnIndex)"
+            matTooltip="Insert Column Before"
+            matTooltipPosition="above"
+            i18n-matTooltip
+            aria-label="Insert Column Before"
+            i18n-aria-label>
+          <mat-icon class="rotate90">loupe</mat-icon>
+        </button>
+        <button mat-raised-button
+            color="primary"
+            class="column-buttons"
+            (click)="deleteColumn(columnIndex)"
+            matTooltip="Delete Column"
+            matTooltipPosition="above"
+            i18n-matTooltip
+            aria-label="Delete"
+            i18n-aria-label>
+          <mat-icon>delete</mat-icon>
+        </button>
+        <button mat-raised-button
+            color="primary"
+            class="column-buttons"
+            *ngIf="isLast"
+            (click)="insertColumn(columnIndex + 1)"
+            matTooltip="Insert Column After"
+            matTooltipPosition="above"
+            i18n-matTooltip
+            aria-label="Insert Column After"
+            i18n-aria-label>
+          <mat-icon>loupe</mat-icon>
+        </button>
+        <div *ngIf="!isLast" class="spacer">
+        </div>
+      </div>
+    </td>
+  </tr>
+  <tr *ngFor="let row of authoringComponentContent.tableData; index as rowIndex; last as isLast">
+    <td class="outer-cell-container" fxLayout="column" fxLayoutAlign="center center">
+      <button mat-raised-button
+          color="primary"
+          class="row-buttons"
+          (click)="insertRow(rowIndex)"
+          matTooltip="Insert Row Before"
+          matTooltipPosition="above"
+          i18n-matTooltip
+          aria-label="Insert Row Before"
+          i18n-aria-label>
+        <mat-icon class="rotate270">loupe</mat-icon>
+      </button>
+      <button mat-raised-button
+          color="primary"
+          class="row-buttons"
+          (click)="deleteRow(rowIndex)"
+          matTooltip="Delete Row"
+          matTooltipPosition="above"
+          i18n-matTooltip
+          aria-label="Delete"
+          i18n-aria-label>
+        <mat-icon>delete</mat-icon>
+      </button>
+      <button mat-raised-button
+          color="primary"
+          class="row-buttons"
+          *ngIf="isLast"
+          (click)="insertRow(rowIndex + 1)"
+          matTooltip="Insert Row After"
+          matTooltipPosition="above"
+          i18n-matTooltip
+          aria-label="Insert Row After"
+          i18n-aria-label>
+        <mat-icon>loupe</mat-icon>
+      </button>
+    </td>
+    <td *ngFor="let cell of row"
+        class="inner-cell-container">
+      <div fxLayout="column" fxLayoutAlign="center center">
+        <mat-form-field class="cell-text">
+          <input matInput
+              [(ngModel)]="cell.text"
+              (ngModelChange)="inputChange.next($event)"
+              ng-model-options="{ debounce: 1000 }"
+              aria-label="Text"/>
+        </mat-form-field>
+        <mat-checkbox
+            color="primary"
+            [(ngModel)]="cell.editable"
+            (ngModelChange)="componentChanged()"
+            i18n>
+          Editable
+        </mat-checkbox>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td class="blank-row-cell"></td>
+    <td *ngFor="let x of [].constructor(getNumColumnsInTableData())"
+        class="blank-row-cell">
+    </td>
+  </tr>
+  <tr>
+    <td class="outer-cell-container">
+      <div fxLayoutAlign="center center">
+        Optional
+      </div>
+    </td>
+    <td *ngFor="let x of [].constructor(getNumColumnsInTableData()); index as cellIndex"
+        class="inner-cell-container">
+      <div fxLayoutAlign="center center">
+        <mat-form-field>
+          <mat-label i18n>Column Cell Size</mat-label>
+          <input matInput
+              type="number"
+              min="1"
+              [(ngModel)]="columnCellSizes[cellIndex]"
+              (ngModelChange)="columnSizeChanged(cellIndex)"/>
+        </mat-form-field>
+      </div>
+    </td>
+  </tr>
+</table>
+<div fxLayout="row">
+  <button mat-raised-button
+      color="primary"
+      class="make-all-cells-editable-buttons"
+      (click)="setAllCellsEditable()"
+      i18n>
+    Make All Cells Editable
+  </button>
+  <button mat-raised-button
+      color="primary"
+      class="make-all-cells-editable-buttons"
+      (click)="setAllCellsUneditable()"
+      i18n>
+    Make All Cells Uneditable
+  </button>
+</div>
\ No newline at end of file
diff --git a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.scss b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.scss
new file mode 100644
index 0000000000..cdd97d67ff
--- /dev/null
+++ b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.scss
@@ -0,0 +1,62 @@
+.prompt {
+  width: 100%;
+}
+
+.size-input {
+  width: 120px;
+  margin-right: 20px;
+}
+
+.table {
+  width: 100%;
+}
+
+.rotate90 {
+  -webkit-transform: rotate(90deg);
+  transform: rotate(90deg);
+}
+
+.rotate270 {
+  -webkit-transform: rotate(270deg);
+  transform: rotate(270deg);
+}
+
+.column-buttons {
+  margin: 5px;
+}
+
+.row-buttons {
+  margin-top: 5px;
+  margin-bottom: 5px;
+}
+
+.spacer {
+  width: 56px;
+}
+
+.outer-cell-container {
+  border: 1px solid black;
+  padding: 5px;
+  width: auto;
+}
+
+.inner-cell-container {
+  border: 1px solid black;
+  padding: 10px;
+}
+
+.cell-text {
+  width: 100%;
+}
+
+.blank-row-cell {
+  height: 20px;
+}
+
+::ng-deep .mat-form-field-infix {
+  width: auto !important;
+}
+
+.make-all-cells-editable-buttons {
+  margin: 10px;
+}
\ No newline at end of file
diff --git a/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts
new file mode 100644
index 0000000000..0d4cfa8f31
--- /dev/null
+++ b/src/main/webapp/wise5/components/table/table-authoring/table-authoring.component.ts
@@ -0,0 +1,361 @@
+'use strict';
+
+import { Component } from '@angular/core';
+import { Subject, Subscription } from 'rxjs';
+import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
+import { ProjectAssetService } from '../../../../site/src/app/services/projectAssetService';
+import { ComponentAuthoring } from '../../../authoringTool/components/component-authoring.component';
+import { ConfigService } from '../../../services/configService';
+import { NodeService } from '../../../services/nodeService';
+import { TeacherProjectService } from '../../../services/teacherProjectService';
+
+@Component({
+  selector: 'table-authoring',
+  templateUrl: 'table-authoring.component.html',
+  styleUrls: ['table-authoring.component.scss']
+})
+export class TableAuthoring extends ComponentAuthoring {
+  columnCellSizes: any;
+
+  numColumnsChange: Subject<number> = new Subject<number>();
+  numRowsChange: Subject<number> = new Subject<number>();
+  globalCellSizeChange: Subject<number> = new Subject<number>();
+  inputChange: Subject<string> = new Subject<string>();
+
+  numColumnsChangeSubscription: Subscription;
+  numRowsChangeSubscription: Subscription;
+  globalCellSizeChangeSubscription: Subscription;
+  inputChangeSubscription: Subscription;
+
+  constructor(
+    protected ConfigService: ConfigService,
+    protected NodeService: NodeService,
+    protected ProjectAssetService: ProjectAssetService,
+    protected ProjectService: TeacherProjectService
+  ) {
+    super(ConfigService, NodeService, ProjectAssetService, ProjectService);
+    this.numColumnsChangeSubscription = this.numColumnsChange
+      .pipe(debounceTime(1000), distinctUntilChanged())
+      .subscribe(() => {
+        this.tableNumColumnsChanged();
+      });
+    this.numRowsChangeSubscription = this.numRowsChange
+      .pipe(debounceTime(1000), distinctUntilChanged())
+      .subscribe(() => {
+        this.tableNumRowsChanged();
+      });
+    this.globalCellSizeChangeSubscription = this.globalCellSizeChange
+      .pipe(debounceTime(1000), distinctUntilChanged())
+      .subscribe(() => {
+        this.componentChanged();
+      });
+    this.inputChangeSubscription = this.inputChange
+      .pipe(debounceTime(1000), distinctUntilChanged())
+      .subscribe(() => {
+        this.componentChanged();
+      });
+  }
+
+  ngOnInit() {
+    super.ngOnInit();
+    this.columnCellSizes = this.parseColumnCellSizes(this.componentContent);
+  }
+
+  ngOnDestroy() {
+    super.ngOnDestroy();
+    this.unsubscribeAll();
+  }
+
+  unsubscribeAll() {
+    this.numColumnsChangeSubscription.unsubscribe();
+    this.numRowsChangeSubscription.unsubscribe();
+    this.globalCellSizeChangeSubscription.unsubscribe();
+    this.inputChangeSubscription.unsubscribe();
+  }
+
+  tableNumRowsChanged(): void {
+    const oldValue = this.getNumRowsInTableData();
+    const newValue = this.authoringComponentContent.numRows;
+    if (newValue < oldValue) {
+      if (this.areRowsAfterEmpty(newValue)) {
+        this.tableSizeChanged();
+      } else {
+        if (confirm($localize`Are you sure you want to decrease the number of rows?`)) {
+          this.tableSizeChanged();
+        } else {
+          this.authoringComponentContent.numRows = oldValue;
+        }
+      }
+    } else {
+      this.tableSizeChanged();
+    }
+  }
+
+  areRowsAfterEmpty(rowIndex: number): boolean {
+    const oldNumRows = this.getNumRowsInTableData();
+    for (let r = rowIndex; r < oldNumRows; r++) {
+      if (!this.isRowEmpty(r)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  isRowEmpty(rowIndex: number): boolean {
+    const tableData = this.authoringComponentContent.tableData;
+    for (const cell of tableData[rowIndex]) {
+      if (!this.isEmpty(cell.text)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  tableNumColumnsChanged(): void {
+    const oldValue = this.getNumColumnsInTableData();
+    const newValue = this.authoringComponentContent.numColumns;
+    if (newValue < oldValue) {
+      if (this.areColumnsAfterEmpty(newValue)) {
+        this.tableSizeChanged();
+      } else {
+        if (confirm($localize`Are you sure you want to decrease the number of columns?`)) {
+          this.tableSizeChanged();
+        } else {
+          this.authoringComponentContent.numColumns = oldValue;
+        }
+      }
+    } else {
+      this.tableSizeChanged();
+    }
+  }
+
+  areColumnsAfterEmpty(columnIndex: number): boolean {
+    const oldNumColumns = this.getNumColumnsInTableData();
+    for (let c = columnIndex; c < oldNumColumns; c++) {
+      if (!this.isColumnEmpty(c)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  isColumnEmpty(columnIndex: number): boolean {
+    for (const row of this.authoringComponentContent.tableData) {
+      const cell = row[columnIndex];
+      if (!this.isEmpty(cell.text)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  isEmpty(txt: string): boolean {
+    return txt == null || txt == '';
+  }
+
+  tableSizeChanged(): void {
+    this.authoringComponentContent.tableData = this.getUpdatedTable(
+      this.authoringComponentContent.numRows,
+      this.authoringComponentContent.numColumns
+    );
+    this.componentChanged();
+  }
+
+  /**
+   * Create a table with the given dimensions. Populate the cells with the cells from the old table.
+   * @param newNumRows the number of rows in the new table
+   * @param newNumColumns the number of columns in the new table
+   * @returns a new table
+   */
+  getUpdatedTable(newNumRows: number, newNumColumns: number): any {
+    const newTable = [];
+    for (let r = 0; r < newNumRows; r++) {
+      const newRow = [];
+      for (let c = 0; c < newNumColumns; c++) {
+        let cell = this.getCellObjectFromTableData(c, r);
+        if (cell == null) {
+          cell = this.createEmptyCell();
+        }
+        newRow.push(cell);
+      }
+      newTable.push(newRow);
+    }
+    return newTable;
+  }
+
+  /**
+   * Get the cell object at the given x, y location
+   * @param x the column number (zero indexed)
+   * @param y the row number (zero indexed)
+   * @returns the cell at the given x, y location or null if there is none
+   */
+  getCellObjectFromTableData(x: number, y: number): any {
+    let cellObject = null;
+    const tableData = this.authoringComponentContent.tableData;
+    if (tableData != null) {
+      const row = tableData[y];
+      if (row != null) {
+        cellObject = row[x];
+      }
+    }
+    return cellObject;
+  }
+
+  createEmptyCell(): any {
+    return {
+      text: '',
+      editable: true,
+      size: null
+    };
+  }
+
+  insertRow(rowIndex: number): void {
+    const tableData = this.authoringComponentContent.tableData;
+    const newRow = [];
+    const numColumns = this.authoringComponentContent.numColumns;
+    for (let c = 0; c < numColumns; c++) {
+      const newCell = this.createEmptyCell();
+      const cellSize = this.columnCellSizes[c];
+      if (cellSize != null) {
+        newCell.size = cellSize;
+      }
+      newRow.push(newCell);
+    }
+    tableData.splice(rowIndex, 0, newRow);
+    this.authoringComponentContent.numRows++;
+    this.componentChanged();
+  }
+
+  deleteRow(rowIndex: number): void {
+    if (confirm($localize`Are you sure you want to delete this row?`)) {
+      const tableData = this.authoringComponentContent.tableData;
+      if (tableData != null) {
+        tableData.splice(rowIndex, 1);
+        this.authoringComponentContent.numRows--;
+      }
+      this.componentChanged();
+    }
+  }
+
+  insertColumn(columnIndex: number): void {
+    const tableData = this.authoringComponentContent.tableData;
+    const numRows = this.authoringComponentContent.numRows;
+    for (let r = 0; r < numRows; r++) {
+      const row = tableData[r];
+      const newCell = this.createEmptyCell();
+      row.splice(columnIndex, 0, newCell);
+    }
+    this.authoringComponentContent.numColumns++;
+    this.parseColumnCellSizes(this.authoringComponentContent);
+    this.componentChanged();
+  }
+
+  deleteColumn(columnIndex: number): void {
+    if (confirm($localize`Are you sure you want to delete this column?`)) {
+      const tableData = this.authoringComponentContent.tableData;
+      const numRows = this.authoringComponentContent.numRows;
+      for (let r = 0; r < numRows; r++) {
+        const row = tableData[r];
+        row.splice(columnIndex, 1);
+      }
+      this.authoringComponentContent.numColumns--;
+      this.parseColumnCellSizes(this.authoringComponentContent);
+      this.componentChanged();
+    }
+  }
+
+  /**
+   * Get the number of rows in the table data. This is slightly different from just getting the
+   * numRows field in the component content. Usually the number of rows will be the same. In some
+   * cases it can be different such as during authoring immediately after the author changes the
+   * number of rows using the number of rows input.
+   * @return {number} The number of rows in the table data.
+   */
+  getNumRowsInTableData(): number {
+    return this.authoringComponentContent.tableData.length;
+  }
+
+  /**
+   * Get the number of columns in the table data. This is slightly different from just getting the
+   * numColumns field in the component content. Usually the number of columns will be the same. In
+   * some cases it can be different such as during authoring immediately after the author changes
+   * the number of columns using the number of columns input.
+   * @return {number} The number of columns in the table data.
+   */
+  getNumColumnsInTableData(): number {
+    const tableData = this.authoringComponentContent.tableData;
+    if (tableData.length > 0) {
+      return tableData[0].length;
+    }
+    return 0;
+  }
+
+  setAllCellsUneditable(): void {
+    this.setAllCellsIsEditable(false);
+    this.componentChanged();
+  }
+
+  setAllCellsEditable(): void {
+    this.setAllCellsIsEditable(true);
+    this.componentChanged();
+  }
+
+  setAllCellsIsEditable(isEditable: boolean): void {
+    for (const row of this.authoringComponentContent.tableData) {
+      for (const cell of row) {
+        cell.editable = isEditable;
+      }
+    }
+  }
+
+  /**
+   * Parse the column cell sizes. We will get the column cell sizes by looking at the size value of
+   * each cell in the first row.
+   * @param componentContent the component content
+   */
+  parseColumnCellSizes(componentContent: any): any {
+    const columnCellSizes = {};
+    const tableData = componentContent.tableData;
+    const firstRow = tableData[0];
+    if (firstRow != null) {
+      for (let x = 0; x < firstRow.length; x++) {
+        const cell = firstRow[x];
+        columnCellSizes[x] = cell.size;
+      }
+    }
+    return columnCellSizes;
+  }
+
+  columnSizeChanged(index: number): void {
+    let cellSize = this.columnCellSizes[index];
+    if (cellSize == '') {
+      cellSize = null;
+    }
+    this.setColumnCellSizes(index, cellSize);
+  }
+
+  setColumnCellSizes(column: number, size: number): void {
+    const tableData = this.authoringComponentContent.tableData;
+    for (let r = 0; r < tableData.length; r++) {
+      const row = tableData[r];
+      const cell = row[column];
+      if (cell != null) {
+        cell.size = size;
+      }
+    }
+    this.componentChanged();
+  }
+
+  automaticallySetConnectedComponentFieldsIfPossible(connectedComponent) {
+    if (connectedComponent.type === 'importWork' && connectedComponent.action == null) {
+      connectedComponent.action = 'merge';
+    } else if (connectedComponent.type === 'showWork') {
+      connectedComponent.action = null;
+    }
+  }
+
+  connectedComponentTypeChanged(connectedComponent) {
+    this.automaticallySetConnectedComponentFieldsIfPossible(connectedComponent);
+    this.componentChanged();
+  }
+}
diff --git a/src/main/webapp/wise5/components/table/tableAuthoring.ts b/src/main/webapp/wise5/components/table/tableAuthoring.ts
deleted file mode 100644
index beef4e7fef..0000000000
--- a/src/main/webapp/wise5/components/table/tableAuthoring.ts
+++ /dev/null
@@ -1,428 +0,0 @@
-'use strict';
-
-import { Directive } from '@angular/core';
-import { EditComponentController } from '../../authoringTool/components/editComponentController';
-
-@Directive()
-class TableAuthoringController extends EditComponentController {
-  columnCellSizes: any;
-
-  static $inject = [
-    '$filter',
-    'ConfigService',
-    'NodeService',
-    'NotificationService',
-    'ProjectAssetService',
-    'ProjectService',
-    'UtilService'
-  ];
-
-  constructor(
-    $filter,
-    ConfigService,
-    NodeService,
-    NotificationService,
-    ProjectAssetService,
-    ProjectService,
-    UtilService
-  ) {
-    super(
-      $filter,
-      ConfigService,
-      NodeService,
-      NotificationService,
-      ProjectAssetService,
-      ProjectService,
-      UtilService
-    );
-  }
-
-  $onInit() {
-    super.$onInit();
-    this.columnCellSizes = this.parseColumnCellSizes(this.componentContent);
-  }
-
-  tableNumRowsChanged(oldValue: number): void {
-    if (this.authoringComponentContent.numRows < oldValue) {
-      if (this.areRowsAfterEmpty(this.authoringComponentContent.numRows)) {
-        this.tableSizeChanged();
-      } else {
-        if (confirm(this.$translate('table.areYouSureYouWantToDecreaseTheNumberOfRows'))) {
-          this.tableSizeChanged();
-        } else {
-          this.authoringComponentContent.numRows = oldValue;
-        }
-      }
-    } else {
-      this.tableSizeChanged();
-    }
-  }
-
-  /**
-   * Determine if the rows after the given index are empty.
-   * @param rowIndex The index of the row to start checking at. This value is zero indexed.
-   * @return {boolean} True if the row at the given index and all the rows after are empty.
-   * False if the row at the given index or any row after the row index is not empty.
-   */
-  areRowsAfterEmpty(rowIndex: number): boolean {
-    const oldNumRows = this.getNumRowsInTableData();
-    for (let r = rowIndex; r < oldNumRows; r++) {
-      if (!this.isRowEmpty(r)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Determine if a row has cells that are all empty string.
-   * @param rowIndex The row index. This value is zero indexed.
-   * @returns {boolean} True if the text in all the cells in the row are empty string.
-   * False if the text in any cell in the row is not empty string.
-   */
-  isRowEmpty(rowIndex: number): boolean {
-    const tableData = this.authoringComponentContent.tableData;
-    for (const cell of tableData[rowIndex]) {
-      if (cell.text != null && cell.text != '') {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * The author has changed the number of columns.
-   * @param oldValue The previous number of columns.
-   */
-  tableNumColumnsChanged(oldValue: number): void {
-    if (this.authoringComponentContent.numColumns < oldValue) {
-      // the author is reducing the number of columns
-      if (this.areColumnsAfterEmpty(this.authoringComponentContent.numColumns)) {
-        // the columns that we will delete are empty so we will remove the columns
-        this.tableSizeChanged();
-      } else {
-        if (confirm(this.$translate('table.areYouSureYouWantToDecreaseTheNumberOfColumns'))) {
-          this.tableSizeChanged();
-        } else {
-          this.authoringComponentContent.numColumns = oldValue;
-        }
-      }
-    } else {
-      // the author is increasing the number of columns
-      this.tableSizeChanged();
-    }
-  }
-
-  /**
-   * Determine if the columns after the given index are empty.
-   * @param columnIndex The index of the column to start checking at. This value is zero indexed.
-   * @return {boolean} True if the column at the given index and all the columns after are empty.
-   * False if the column at the given index or any column after the column index is not empty.
-   */
-  areColumnsAfterEmpty(columnIndex: number): boolean {
-    const oldNumColumns = this.getNumColumnsInTableData();
-    for (let c = columnIndex; c < oldNumColumns; c++) {
-      if (!this.isColumnEmpty(c)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Determine if a column has cells that are all empty string.
-   * @param columnIndex The column index. This value is zero indexed.
-   * @returns {boolean} True if the text in all the cells in the column are empty string.
-   * False if the text in any cell in the column is not empty string.
-   */
-  isColumnEmpty(columnIndex: number): boolean {
-    for (const row of this.authoringComponentContent.tableData) {
-      const cell = row[columnIndex];
-      if (cell.text != null && cell.text != '') {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * The table size has changed in the authoring view so we will update it
-   */
-  tableSizeChanged(): void {
-    this.authoringComponentContent.tableData = this.getUpdatedTableSize(
-      this.authoringComponentContent.numRows,
-      this.authoringComponentContent.numColumns
-    );
-    this.componentChanged();
-  }
-
-  /**
-   * Create a table with the given dimensions. Populate the cells with
-   * the cells from the old table.
-   * @param newNumRows the number of rows in the new table
-   * @param newNumColumns the number of columns in the new table
-   * @returns a new table
-   */
-  getUpdatedTableSize(newNumRows: number, newNumColumns: number): any {
-    const newTable = [];
-    for (let r = 0; r < newNumRows; r++) {
-      const newRow = [];
-      for (let c = 0; c < newNumColumns; c++) {
-        let cell = this.getCellObjectFromComponentContent(c, r);
-        if (cell == null) {
-          cell = this.createEmptyCell();
-        }
-        newRow.push(cell);
-      }
-      newTable.push(newRow);
-    }
-    return newTable;
-  }
-
-  /**
-   * Get the cell object at the given x, y location
-   * @param x the column number (zero indexed)
-   * @param y the row number (zero indexed)
-   * @returns the cell at the given x, y location or null if there is none
-   */
-  getCellObjectFromComponentContent(x: number, y: number): any {
-    let cellObject = null;
-    const tableData = this.authoringComponentContent.tableData;
-    if (tableData != null) {
-      const row = tableData[y];
-      if (row != null) {
-        cellObject = row[x];
-      }
-    }
-    return cellObject;
-  }
-
-  createEmptyCell(): any {
-    return {
-      text: '',
-      editable: true,
-      size: null
-    };
-  }
-
-  /**
-   * Insert a row into the table from the authoring view
-   * @param y the row number to insert at
-   */
-  insertRow(y: number): void {
-    const tableData = this.authoringComponentContent.tableData;
-    if (tableData != null) {
-      const newRow = [];
-      const numColumns = this.authoringComponentContent.numColumns;
-      for (let c = 0; c < numColumns; c++) {
-        const newCell = this.createEmptyCell();
-        const cellSize = this.columnCellSizes[c];
-        if (cellSize != null) {
-          newCell.size = cellSize;
-        }
-        newRow.push(newCell);
-      }
-      tableData.splice(y, 0, newRow);
-      this.authoringComponentContent.numRows++;
-    }
-    this.componentChanged();
-  }
-
-  /**
-   * Delete a row in the table from the authoring view
-   * @param y the row number to delete
-   */
-  deleteRow(y: number): void {
-    if (confirm(this.$translate('table.areYouSureYouWantToDeleteThisRow'))) {
-      const tableData = this.authoringComponentContent.tableData;
-      if (tableData != null) {
-        tableData.splice(y, 1);
-        this.authoringComponentContent.numRows--;
-      }
-      this.componentChanged();
-    }
-  }
-
-  /**
-   * Insert a column into the table from the authoring view
-   * @param x the column number to insert at
-   */
-  insertColumn(x: number): void {
-    const tableData = this.authoringComponentContent.tableData;
-    if (tableData != null) {
-      const numRows = this.authoringComponentContent.numRows;
-      for (let r = 0; r < numRows; r++) {
-        const tempRow = tableData[r];
-        if (tempRow != null) {
-          const newCell = this.createEmptyCell();
-          tempRow.splice(x, 0, newCell);
-        }
-      }
-      this.authoringComponentContent.numColumns++;
-      this.parseColumnCellSizes(this.authoringComponentContent);
-    }
-    this.componentChanged();
-  }
-
-  /**
-   * Delete a column in the table from the authoring view
-   * @param x the column number to delete
-   */
-  deleteColumn(x: number): void {
-    if (confirm(this.$translate('table.areYouSureYouWantToDeleteThisColumn'))) {
-      const tableData = this.authoringComponentContent.tableData;
-      if (tableData != null) {
-        const numRows = this.authoringComponentContent.numRows;
-        for (let r = 0; r < numRows; r++) {
-          const tempRow = tableData[r];
-          if (tempRow != null) {
-            tempRow.splice(x, 1);
-          }
-        }
-        this.authoringComponentContent.numColumns--;
-        this.parseColumnCellSizes(this.authoringComponentContent);
-      }
-      this.componentChanged();
-    }
-  }
-
-  /**
-   * Get the number of rows in the table data. This is slightly different from
-   * just getting the numRows field in the component content. Usually the
-   * number of rows will be the same. In some cases it can be different
-   * such as during authoring immediately after the author changes the number
-   * of rows using the number of rows input.
-   * @return {number} The number of rows in the table data.
-   */
-  getNumRowsInTableData(): number {
-    return this.authoringComponentContent.tableData.length;
-  }
-
-  /**
-   * Get the number of columns in the table data. This is slightly different from
-   * just getting the numColumns field in the component content. Usually the
-   * number of columns will be the same. In some cases it can be different
-   * such as during authoring immediately after the author changes the number
-   * of columns using the number of columns input.
-   * @return {number} The number of columns in the table data.
-   */
-  getNumColumnsInTableData(): number {
-    const tableData = this.authoringComponentContent.tableData;
-    if (tableData.length > 0) {
-      return tableData[0].length;
-    }
-    return 0;
-  }
-
-  makeAllCellsUneditable(): void {
-    const tableData = this.authoringComponentContent.tableData;
-    if (tableData != null) {
-      for (let r = 0; r < tableData.length; r++) {
-        const row = tableData[r];
-        if (row != null) {
-          for (let c = 0; c < row.length; c++) {
-            const cell = row[c];
-            if (cell != null) {
-              cell.editable = false;
-            }
-          }
-        }
-      }
-    }
-    this.componentChanged();
-  }
-
-  makeAllCellsEditable(): void {
-    const tableData = this.authoringComponentContent.tableData;
-    if (tableData != null) {
-      for (let r = 0; r < tableData.length; r++) {
-        const row = tableData[r];
-        if (row != null) {
-          for (let c = 0; c < row.length; c++) {
-            const cell = row[c];
-            if (cell != null) {
-              cell.editable = true;
-            }
-          }
-        }
-      }
-    }
-    this.componentChanged();
-  }
-
-  /**
-   * Parse the column cell sizes. We will get the column cell sizes by looking
-   * at size value of each column in the first row.
-   * @param componentContent the component content
-   */
-  parseColumnCellSizes(componentContent: any): any {
-    const columnCellSizes = {};
-    const tableData = componentContent.tableData;
-    if (tableData != null) {
-      const firstRow = tableData[0];
-      if (firstRow != null) {
-        for (let x = 0; x < firstRow.length; x++) {
-          const cell = firstRow[x];
-          columnCellSizes[x] = cell.size;
-        }
-      }
-    }
-    return columnCellSizes;
-  }
-
-  columnSizeChanged(index: number): void {
-    if (index != null) {
-      let cellSize = this.columnCellSizes[index];
-      if (cellSize == '') {
-        cellSize = null;
-      }
-      this.setColumnCellSizes(index, cellSize);
-    }
-  }
-
-  /**
-   * Set the cell sizes for all the cells in a column
-   * @param column the column number
-   * @param size the cell size
-   */
-  setColumnCellSizes(column: number, size: number): void {
-    const tableData = this.authoringComponentContent.tableData;
-    if (tableData != null) {
-      for (let r = 0; r < tableData.length; r++) {
-        const row = tableData[r];
-        if (row != null) {
-          const cell = row[column];
-          if (cell != null) {
-            cell.size = size;
-          }
-        }
-      }
-    }
-    this.componentChanged();
-  }
-
-  automaticallySetConnectedComponentFieldsIfPossible(connectedComponent) {
-    if (connectedComponent.type === 'importWork' && connectedComponent.action == null) {
-      connectedComponent.action = 'merge';
-    } else if (connectedComponent.type === 'showWork') {
-      connectedComponent.action = null;
-    }
-  }
-
-  connectedComponentTypeChanged(connectedComponent) {
-    this.automaticallySetConnectedComponentFieldsIfPossible(connectedComponent);
-    this.componentChanged();
-  }
-}
-
-const TableAuthoring = {
-  bindings: {
-    nodeId: '@',
-    componentId: '@'
-  },
-  controller: TableAuthoringController,
-  controllerAs: 'tableController',
-  templateUrl: 'wise5/components/table/authoring.html'
-};
-
-export default TableAuthoring;
diff --git a/src/main/webapp/wise5/components/table/tableAuthoringComponentModule.ts b/src/main/webapp/wise5/components/table/tableAuthoringComponentModule.ts
index 60f9c96360..c202b806cd 100644
--- a/src/main/webapp/wise5/components/table/tableAuthoringComponentModule.ts
+++ b/src/main/webapp/wise5/components/table/tableAuthoringComponentModule.ts
@@ -2,14 +2,17 @@
 
 import * as angular from 'angular';
 import { TableService } from './tableService';
-import { downgradeInjectable } from '@angular/upgrade/static';
-import TableAuthoring from './tableAuthoring';
+import { downgradeComponent, downgradeInjectable } from '@angular/upgrade/static';
 import { EditTableAdvancedComponent } from './edit-table-advanced/edit-table-advanced.component';
+import { TableAuthoring } from './table-authoring/table-authoring.component';
 
 const tableAuthoringComponentModule = angular
   .module('tableAuthoringComponentModule', ['pascalprecht.translate'])
   .service('TableService', downgradeInjectable(TableService))
-  .component('tableAuthoring', TableAuthoring)
+  .directive(
+    'tableAuthoring',
+    downgradeComponent({ component: TableAuthoring }) as angular.IDirectiveFactory
+  )
   .component('editTableAdvanced', EditTableAdvancedComponent)
   .config([
     '$translatePartialLoaderProvider',
diff --git a/src/main/webapp/wise5/services/milestoneService.ts b/src/main/webapp/wise5/services/milestoneService.ts
index 47580d6e77..561e31ed79 100644
--- a/src/main/webapp/wise5/services/milestoneService.ts
+++ b/src/main/webapp/wise5/services/milestoneService.ts
@@ -9,6 +9,7 @@ import { TeacherDataService } from './teacherDataService';
 import { UtilService } from './utilService';
 import { Injectable } from '@angular/core';
 import { UpgradeModule } from '@angular/upgrade/static';
+import { MilestoneDetailsDialog } from '../classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog';
 
 @Injectable()
 export class MilestoneService {
@@ -511,40 +512,12 @@ export class MilestoneService {
   }
 
   showMilestoneDetails(milestone: any, $event: any, hideStudentWork: boolean = false) {
-    const title = this.getTranslation('MILESTONE_DETAILS_TITLE', {
-      name: milestone.name
-    });
-    const template = `<md-dialog class="dialog--wider">
-          <md-toolbar>
-            <div class="md-toolbar-tools">
-              <h2>${title}</h2>
-            </div>
-          </md-toolbar>
-          <md-dialog-content class="gray-lighter-bg md-dialog-content">
-            <milestone-details milestone="milestone"
-                               hide-student-work="hideStudentWork"
-                               on-show-workgroup="onShowWorkgroup(value)"
-                               on-visit-node-grading="onVisitNodeGrading()"></milestone-details>
-          </md-dialog-content>
-          <md-dialog-actions layout="row" layout-align="start center">
-            <span flex></span>
-            <md-button class="md-primary"
-                       ng-click="edit()"
-                       ng-if="milestone.type !== 'milestoneReport'"
-                       aria-label="{{ ::'EDIT' | translate }}">
-              {{ ::'EDIT' | translate }}
-            </md-button>
-            <md-button class="md-primary"
-                       ng-click="close()"
-                       aria-label="{{ ::'CLOSE' | translate }}">
-              {{ ::'CLOSE' | translate }}
-            </md-button>
-          </md-dialog-actions>
-        </md-dialog>`;
     this.upgrade.$injector.get('$mdDialog').show({
       parent: angular.element(document.body),
-      template: template,
-      ariaLabel: title,
+      templateUrl:
+        'wise5/classroomMonitor/classroomMonitorComponents/milestones/milestoneDetailsDialog/milestoneDetailsDialog.html',
+      controller: MilestoneDetailsDialog,
+      controllerAs: '$ctrl',
       fullscreen: true,
       multiple: true,
       targetEvent: $event,
@@ -554,73 +527,7 @@ export class MilestoneService {
         $event: $event,
         milestone: milestone,
         hideStudentWork: hideStudentWork
-      },
-      controller: [
-        '$scope',
-        '$state',
-        '$mdDialog',
-        'milestone',
-        '$event',
-        'TeacherDataService',
-        function DialogController(
-          $scope,
-          $state,
-          $mdDialog,
-          milestone,
-          $event,
-          TeacherDataService
-        ) {
-          $scope.milestone = milestone;
-          $scope.hideStudentWork = hideStudentWork;
-          $scope.event = $event;
-          $scope.close = function () {
-            $scope.saveMilestoneClosedEvent();
-            $mdDialog.hide();
-          };
-          $scope.edit = function () {
-            $mdDialog.hide({
-              milestone: $scope.milestone,
-              action: 'edit',
-              $event: $event
-            });
-          };
-          $scope.onShowWorkgroup = function (workgroup: any) {
-            $scope.saveMilestoneClosedEvent();
-            $mdDialog.hide();
-            TeacherDataService.setCurrentWorkgroup(workgroup);
-            $state.go('root.nodeProgress');
-          };
-          $scope.onVisitNodeGrading = function () {
-            $mdDialog.hide();
-          };
-          $scope.saveMilestoneOpenedEvent = function () {
-            $scope.saveMilestoneEvent('MilestoneOpened');
-          };
-          $scope.saveMilestoneClosedEvent = function () {
-            $scope.saveMilestoneEvent('MilestoneClosed');
-          };
-          $scope.saveMilestoneEvent = function (event: any) {
-            const context = 'ClassroomMonitor',
-              nodeId = null,
-              componentId = null,
-              componentType = null,
-              category = 'Navigation',
-              data = { milestoneId: $scope.milestone.id },
-              projectId = null;
-            TeacherDataService.saveEvent(
-              context,
-              nodeId,
-              componentId,
-              componentType,
-              category,
-              event,
-              data,
-              projectId
-            );
-          };
-          $scope.saveMilestoneOpenedEvent();
-        }
-      ]
+      }
     });
   }
 }
diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIControllerTest.java
new file mode 100644
index 0000000000..b66900f8ff
--- /dev/null
+++ b/src/test/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIControllerTest.java
@@ -0,0 +1,108 @@
+package org.wise.portal.presentation.web.controllers.user;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.HashMap;
+
+import org.easymock.TestSubject;
+import org.junit.Test;
+import org.wise.portal.presentation.web.exception.InvalidPasswordExcpetion;
+
+public class GoogleUserAPIControllerTest extends UserAPIControllerTest {
+
+  @TestSubject
+  private GoogleUserAPIController controller = new GoogleUserAPIController();
+
+  @Test
+  public void isGoogleIdExist_GoogleUserExists_ReturnTrue() {
+    expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1);
+    replay(userService);
+    assertTrue(controller.isGoogleIdExist(STUDENT1_GOOGLE_ID));
+    verify(userService);
+  }
+
+  @Test
+  public void isGoogleIdExist_InvalidGoogleUserId_ReturnFalse() {
+    String invalidGoogleId = "google-id-not-exists-in-db";
+    expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null);
+    replay(userService);
+    assertFalse(controller.isGoogleIdExist(invalidGoogleId));
+    verify(userService);
+  }
+
+  @Test
+  public void isGoogleIdMatches_GoogleUserIdAndUserIdMatch_ReturnTrue() {
+    expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1);
+    replay(userService);
+    assertTrue(controller.isGoogleIdMatches(STUDENT1_GOOGLE_ID, student1Id.toString()));
+    verify(userService);
+  }
+
+  @Test
+  public void isGoogleIdMatches_InvalidGoogleUserId_ReturnFalse() {
+    String invalidGoogleId = "google-id-not-exists-in-db";
+    expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null);
+    replay(userService);
+    assertFalse(controller.isGoogleIdMatches(invalidGoogleId, student1Id.toString()));
+    verify(userService);
+  }
+
+  @Test
+  public void isGoogleIdMatches_GoogleUserIdAndUserIdDoNotMatch_ReturnFalse() {
+    expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(teacher1);
+    replay(userService);
+    assertFalse(controller.isGoogleIdMatches(STUDENT1_GOOGLE_ID, teacher1.toString()));
+    verify(userService);
+  }
+
+  @Test
+  public void getUserByGoogleId_GoogleUserExists_ReturnSuccessResponse() {
+    expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1);
+    replay(userService);
+    HashMap<String, Object> response = controller.getUserByGoogleId(STUDENT1_GOOGLE_ID);
+    assertEquals("success", response.get("status"));
+    assertEquals(student1.getId(), response.get("userId"));
+    verify(userService);
+  }
+
+  @Test
+  public void getUserByGoogleId_InvalidGoogleUserId_ReturnErrorResponse() {
+    String invalidGoogleId = "google-id-not-exists-in-db";
+    expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null);
+    replay(userService);
+    HashMap<String, Object> response = controller.getUserByGoogleId(invalidGoogleId);
+    assertEquals("error", response.get("status"));
+    verify(userService);
+  }
+
+  @Test
+  public void unlinkGoogleAccount_InvalidNewPassword_ThrowException() {
+    expect(userService.retrieveUserByUsername(STUDENT_USERNAME)).andReturn(student1);
+    replay(userService);
+    String newPass = "";
+    try {
+      controller.unlinkGoogleAccount(studentAuth, newPass);
+      fail("InvalidPasswordException was expected");
+    } catch (Exception e) {
+    }
+  }
+
+  @Test
+  public void unlinkGoogleAccount_ValidNewPassword_ReturnUpdatedUserMap()
+      throws InvalidPasswordExcpetion {
+    String newPassword = "my new pass";
+    assertTrue(student1.getUserDetails().isGoogleUser());
+    expect(userService.retrieveUserByUsername(STUDENT_USERNAME)).andReturn(student1).times(2);
+    expect(userService.updateUserPassword(student1, newPassword)).andReturn(student1);
+    expect(appProperties.getProperty("send_email_enabled", "false")).andReturn("false");
+    replay(userService, appProperties);
+    controller.unlinkGoogleAccount(studentAuth, newPassword);
+    verify(userService, appProperties);
+  }
+}
diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java
index 251fcb0b47..c5b901ce8f 100644
--- a/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java
+++ b/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java
@@ -143,68 +143,6 @@ public void getSupportedLanguages_ThreeSupportedLocales_ReturnLanguageArray() {
     verify(appProperties);
   }
 
-  @Test
-  public void isGoogleIdExist_GoogleUserExists_ReturnTrue() {
-    expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1);
-    replay(userService);
-    assertTrue(userAPIController.isGoogleIdExist(STUDENT1_GOOGLE_ID));
-    verify(userService);
-  }
-
-  @Test
-  public void isGoogleIdExist_InvalidGoogleUserId_ReturnFalse() {
-    String invalidGoogleId = "google-id-not-exists-in-db";
-    expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null);
-    replay(userService);
-    assertFalse(userAPIController.isGoogleIdExist(invalidGoogleId));
-    verify(userService);
-  }
-
-  @Test
-  public void isGoogleIdMatches_GoogleUserIdAndUserIdMatch_ReturnTrue() {
-    expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1);
-    replay(userService);
-    assertTrue(userAPIController.isGoogleIdMatches(STUDENT1_GOOGLE_ID, student1Id.toString()));
-    verify(userService);
-  }
-
-  @Test
-  public void isGoogleIdMatches_InvalidGoogleUserId_ReturnFalse() {
-    String invalidGoogleId = "google-id-not-exists-in-db";
-    expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null);
-    replay(userService);
-    assertFalse(userAPIController.isGoogleIdMatches(invalidGoogleId, student1Id.toString()));
-    verify(userService);
-  }
-
-  @Test
-  public void isGoogleIdMatches_GoogleUserIdAndUserIdDoNotMatch_ReturnFalse() {
-    expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(teacher1);
-    replay(userService);
-    assertFalse(userAPIController.isGoogleIdMatches(STUDENT1_GOOGLE_ID, teacher1.toString()));
-    verify(userService);
-  }
-
-  @Test
-  public void getUserByGoogleId_GoogleUserExists_ReturnSuccessResponse() {
-    expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1);
-    replay(userService);
-    HashMap<String, Object> response = userAPIController.getUserByGoogleId(STUDENT1_GOOGLE_ID);
-    assertEquals("success", response.get("status"));
-    assertEquals(student1.getId(), response.get("userId"));
-    verify(userService);
-  }
-
-  @Test
-  public void getUserByGoogleId_InvalidGoogleUserId_ReturnErrorResponse() {
-    String invalidGoogleId = "google-id-not-exists-in-db";
-    expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null);
-    replay(userService);
-    HashMap<String, Object> response = userAPIController.getUserByGoogleId(invalidGoogleId);
-    assertEquals("error", response.get("status"));
-    verify(userService);
-  }
-
   @Test
   public void isNameValid_InvalidName_ReturnFalse() {
     assertFalse(userAPIController.isNameValid(""));