---
title: Continue an Incomplete Survey
product: Form Library
description: Learn how to enable users to continue incomplete surveys with this free demo for JavaScript. Ensure seamless survey experience across sessions by saving and restoring survey responses from localStorage.
framework: Angular
source: https://surveyjs.io/form-library/examples/save-and-restore-user-responses-to-complete-survey/angular
index: https://surveyjs.io/form-library/examples/overview.md
---

# Continue an Incomplete Survey (Angular)

Respondents may not complete your survey in a single session. In this case, you can restore their answers from the previous session next time they get to the survey. Incomplete results can be loaded from your database or the browser's [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). This demo shows how to save and restore incomplete survey results and the last visited question and page from the `localStorage`. To test this functionality, answer a couple of questions and reload the page. Your answers should persist. For information on how to save incomplete results on a server, refer to the following help topic: [Restore Survey Progress from a Database](/form-library/documentation/how-to-save-and-restore-incomplete-survey#restore-survey-progress-from-a-database). 

## Save Incomplete Survey Results

To save incomplete results, implement functions that send survey data and UI state to your server or store them in the `localStorage` (see the `saveSurveyData` and `saveSurveyUIState` functions in the code). Call these functions inside the `SurveyModel`'s [`onValueChanged`](https://surveyjs.io/form-library/documentation/api-reference/survey-data-model#onValueChanged) and [`onUIStateChanged`](https://surveyjs.io/form-library/documentation/api-reference/survey-data-model#onUIStateChanged) event handlers to capture updates whenever users change a value or modify the UI (for example, expand/collapse a question box or switch pages). If you use the `localStorage`, also handle the [`onComplete`](https://surveyjs.io/form-library/documentation/api-reference/survey-data-model#onComplete) event to submit the final results to the server and remove them from the `localStorage`, as they no longer need to be restored.

> `localStorage` is limited to 5 MB of data per domain. If you expect incomplete responses to exceed this limit (which may happen if they contain encoded images or files), [store them in a database](/form-library/documentation/how-to-save-and-restore-incomplete-survey#restore-survey-progress-from-a-database).

## Restore Survey Progress

To restore results, retrieve the saved data from your server or `localStorage` and assign it to the `SurveyModel`'s [`data`](https://surveyjs.io/form-library/documentation/api-reference/survey-data-model#data) property. If you've also stored the UI state, assign it to the [`uiState`](https://surveyjs.io/form-library/documentation/api-reference/survey-data-model#uiState) property.

## Files

### `src/app/components/json.ts`

```ts
export const json = {
  "pages": [{
    "name": "page1",
    "elements": [{
      "type": "matrix",
      "name": "qualities",
      "title": "Please indicate if you agree or disagree with the following statements",
      "columns": [{
        "value": 5,
        "text": "Strongly agree"
      }, {
        "value": 4,
        "text": "Agree"
      }, {
        "value": 3,
        "text": "Neutral"
      }, {
        "value": 2,
        "text": "Disagree"
      }, {
        "value": 1,
        "text": "Strongly disagree"
      }],
      "rows": [{
        "value": "affordable",
        "text": "Product is affordable"
      }, {
        "value": "does-what-it-claims",
        "text": "Product does what it claims"
      }, {
        "value": "better-than-others",
        "text": "Product is better than other products on the market"
      },{
        "value": "easy-to-use",
        "text": "Product is easy to use"
      }]
    }, {
      "type": "rating",
      "name": "satisfaction-score",
      "title": "How satisfied are you with our product?",
      "minRateDescription": "Not satisfied",
      "maxRateDescription": "Completely satisfied"
    }, {
      "type": "checkbox",
      "name": "favourite-features",
      "title": "Which product features do you value most?",
      "description": "The options are presented in a randomized order, which is preserved in the UI state.",
      "choices": [
        "Ease of use",
        "Performance",
        "Customization options",
        "Integrations",
        "Customer support"
      ],
      "choicesOrder": "random"
    }, {
      "type": "rating",
      "name": "recommend",
      "visibleIf": "{satisfaction-score} > 3",
      "title": "How likely are you to recommend our product to a friend or co-worker?",
      "minRateDescription": "Will not recommend",
      "maxRateDescription": "I will recommend"
    }, {
      "type": "comment",
      "name": "suggestions",
      "title": "What would make you more satisfied with our product?"
    }]
  }, {
    "name": "page2",
    "elements": [{
      "type": "radiogroup",
      "name": "price-comparison",
      "title": "Compared to our competitors, do you feel our product is:",
      "choices": [
        "Less expensive",
        "Priced about the same",
        "More expensive",
        "Not sure"
      ]
    }, {
      "type": "radiogroup",
      "name": "current-price",
      "title": "Do you feel our current price is merited by our product?",
      "choices": [{
        "value": "correct",
        "text": "Yes, the price is about right"
      }, {
        "value": "low",
        "text": "No, the price is too low for your product"
      }, {
        "value": "high",
        "text": "No, the price is too high for your product"
      }]
    }, {
      "type": "multipletext",
      "name": "price-limits",
      "title": "What is the highest and lowest price you would pay for a product like ours?",
      "items": [{
        "name": "highest",
        "title": "Highest"
      }, {
        "name": "lowest",
        "title": "Lowest"
      }],
      "itemTitleWidth": "60px"
    }]
  }, {
    "name": "page3",
    "elements": [{
      "type": "text",
      "name": "email",
      "title": "Please leave your email address if you would like us to contact you."
    }]
  }]
};
```

### `src/app/components/survey.component.css`

```css
/* You can add your custom CSS here. */
```

### `src/app/components/survey.component.html`

```html
<survey [model]="model" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; height:100%"></survey>
```

### `src/app/components/survey.component.ts`

```ts
import { Component, OnInit } from "@angular/core";
import { Model } from "survey-core";
import { json } from "./json";
import "./survey.component.css";
import "survey-core/survey-core.min.css";

const STORAGE_ITEM_DATA_KEY = "my-survey-data";
const STORAGE_ITEM_UI_STATE_KEY = "my-survey-state";

function saveSurveyData(survey) {
    window.localStorage.setItem(STORAGE_ITEM_DATA_KEY, JSON.stringify(survey.data));
}

function saveSurveyUIState(survey) {
    window.localStorage.setItem(STORAGE_ITEM_UI_STATE_KEY, JSON.stringify(survey.uiState));
}

function clearStorage() {
    window.localStorage.setItem(STORAGE_ITEM_DATA_KEY, "");
    window.localStorage.setItem(STORAGE_ITEM_UI_STATE_KEY, "");
}
@Component({
    // tslint:disable-next-line:component-selector
    selector: "component-survey",
    templateUrl: "./survey.component.html",
    styleUrls: ["./survey.component.css"]
})
export class SurveyComponent implements OnInit {
    model: Model;
    ngOnInit() {
        const survey = new Model(json);
        // Save survey results and UI state to the local storage
        survey.onValueChanged.add(saveSurveyData);
        survey.onUIStateChanged.add(saveSurveyUIState);
        
        // Restore survey results
        const prevData = window.localStorage.getItem(STORAGE_ITEM_DATA_KEY) || null;
        if (prevData) {
            const data = JSON.parse(prevData);
            survey.data = data;
        }
        
        // Restore the survey UI state
        const prevState = window.localStorage.getItem(STORAGE_ITEM_UI_STATE_KEY) || null;
        if (prevState) {
            const state = JSON.parse(prevState);
            survey.uiState = state;
        }
        
        // Empty the local storage after the survey is completed
        survey.onComplete.add(clearStorage);
        survey.onComplete.add((sender, options) => {
            console.log(JSON.stringify(sender.data, null, 3));
        });
        this.model = survey;
    }
}
```

### `src/app/components/theme.ts`

```ts
export const themeJson = {};
```

### `src/app/app.component.css`

```css

```

### `src/app/app.component.html`

```html
<component-survey></component-survey>
```

### `src/app/app.component.ts`

```ts
import { Component } from "@angular/core";

@Component({
    selector: "app-root",
    templateUrl: "./app.component.html",
    styleUrls: ["./app.component.css"]
})
export class AppComponent {
    title = "CodeSandbox";
}
```

### `src/app/app.module.ts`

```ts
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";

import { AppComponent } from "./app.component";
import { SurveyModule } from "survey-angular-ui";
import { SurveyComponent } from "./components/survey.component";

@NgModule({
    declarations: [AppComponent, SurveyComponent],
    imports: [BrowserModule, SurveyModule],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }
```

### `src/environments/environment.prod.ts`

```ts
export const environment = {
    production: true
};
```

### `src/environments/environment.ts`

```ts
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.

export const environment = {
    production: false
};
```

### `src/index.html`

```html
<app-root></app-root>
```

### `src/main.ts`

```ts
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";

import { AppModule } from "./app/app.module";
import { environment } from "./environments/environment";

if (environment.production) {
    enableProdMode();
}

platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.log(err));
```

### `src/polyfills.ts`

```ts
/**
 * This file includes polyfills needed by Angular and is loaded before the app.
 * You can add your own extra polyfills to this file.
 *
 * This file is divided into 2 sections:
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 *      file.
 *
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 *
 * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
 */

/***************************************************************************************************
 * BROWSER POLYFILLS
 */

/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install classlist.js`.

/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';

/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import "core-js/proposals/reflect-metadata";

/**
 * Required to support Web Animations `@angular/platform-browser/animations`.
 * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
 **/
// import 'web-animations-js';  // Run `npm install web-animations-js`.

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import "zone.js/dist/zone"; // Included with Angular CLI.

/***************************************************************************************************
 * APPLICATION IMPORTS
 */
```

### `src/styles.css`

```css
/* You can add your custom CSS here. */
```

### `src/typings.d.ts`

```ts
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
    id: string;
}
```

### `.angular-cli.json`

```json
{
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [ "assets", "favicon.ico" ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "prefix": "app",
      "styles": [ "styles.css" ],
      "scripts": [ ],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ]
}
```

### `_tsconfig.json`

```json
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "allowSyntheticDefaultImports": true,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "resolveJsonModule": true,
    "target": "es2015",
    "module": "es2020",
    "lib": [
      "es2018",
      "dom"
    ]
  }
}
```

### `package.json`

```json
{
  "name": "surveyjs-angular",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "14.1.1",
    "@angular/cdk": "14.1.1",
    "@angular/common": "14.1.1",
    "@angular/compiler": "14.1.1",
    "@angular/core": "14.1.1",
    "@angular/forms": "14.1.1",
    "@angular/platform-browser": "14.1.1",
    "@angular/platform-browser-dynamic": "14.1.1",
    "@angular/router": "14.1.1",
    "core-js": "3.6.4",
    "rxjs": "6.5.4",
    "survey-core": "latest",
    "survey-angular-ui": "latest",
    "tslib": "1.13.0",
    "zone.js": "0.11.7"
  },
  "devDependencies": {
    "@angular/cli": "1.6.6",
    "@angular/compiler-cli": "^5.2.0",
    "@angular/language-service": "^5.2.0",
    "@types/core-js": "0.9.46",
    "@types/jasmine": "~2.8.3",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "^4.0.1",
    "jasmine-core": "~2.8.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~2.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~4.1.0",
    "tslint": "~5.9.1",
    "typescript": "3.9.7"
  },
  "keywords": [ "angular", "surveyjs" ],
  "description": "SurveyJS-Angular example project"
}
```

## Other Frameworks

- [React](https://surveyjs.io/form-library/examples/save-and-restore-user-responses-to-complete-survey/reactjs.md)
- [Vue 3](https://surveyjs.io/form-library/examples/save-and-restore-user-responses-to-complete-survey/vue3js.md)
- [jQuery](https://surveyjs.io/form-library/examples/save-and-restore-user-responses-to-complete-survey/jquery.md)
- [Vanilla JS](https://surveyjs.io/form-library/examples/save-and-restore-user-responses-to-complete-survey/vanillajs.md)
