You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Ionic 5/Angular实现单页一题表单的方案咨询及示例请求

解决方案:Ionic 5 + Angular 实现单题分步表单

一、关于跨路由拆分<form>的问题

结论:无法直接跨路由拆分<form>标签
Angular路由切换时,原组件会被销毁,DOM树重新构建,<form>元素无法跨越不同路由的组件存在。替代方案是用共享服务存储表单数据,在各路由组件中独立维护表单控件,最后统一提交。

二、单屏单题分步表单(同一组件内)

这种方式适合不需要路由跳转,仅在同一页面切换问题的场景,用Angular响应式表单管理数据,通过控制显示/隐藏切换问题。

1. 创建表单共享服务

// src/app/services/quiz-form.service.ts
import { Injectable } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

@Injectable({ providedIn: 'root' })
export class QuizFormService {
  quizForm: FormGroup;
  currentStep = 0;
  questions = [
    { key: 'q1', label: '你最喜欢的编程语言是?', type: 'text' },
    { key: 'q2', label: '你使用Ionic多久了?', type: 'number' },
    { key: 'q3', label: '你常用的前端框架是?', options: ['Angular', 'React', 'Vue'] }
  ];

  constructor(private fb: FormBuilder) {
    this.initForm();
  }

  initForm() {
    const formControls = {};
    this.questions.forEach(q => {
      formControls[q.key] = ['', Validators.required];
    });
    this.quizForm = this.fb.group(formControls);
  }

  nextStep() {
    if (this.currentStep < this.questions.length - 1) {
      this.currentStep++;
    }
  }

  prevStep() {
    if (this.currentStep > 0) {
      this.currentStep--;
    }
  }

  submitQuiz() {
    if (this.quizForm.valid) {
      console.log('提交的答案:', this.quizForm.value);
      // 此处添加提交到后端的逻辑
    }
  }
}

2. 实现问卷组件

// src/app/quiz/quiz.component.ts
import { Component } from '@angular/core';
import { QuizFormService } from '../services/quiz-form.service';

@Component({
  selector: 'app-quiz',
  templateUrl: './quiz.component.html',
  styleUrls: ['./quiz.component.scss'],
})
export class QuizComponent {
  constructor(public quizService: QuizFormService) {}

  get currentQuestion() {
    return this.quizService.questions[this.quizService.currentStep];
  }

  isLastStep() {
    return this.quizService.currentStep === this.quizService.questions.length - 1;
  }
}

3. 组件模板(Ionic UI)

<!-- src/app/quiz/quiz.component.html -->
<ion-header>
  <ion-toolbar>
    <ion-title>分步问卷</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content class="ion-padding">
  <form [formGroup]="quizService.quizForm">
    <!-- 当前问题区域 -->
    <div *ngIf="currentQuestion">
      <ion-label class="ion-text-wrap">{{ currentQuestion.label }}</ion-label>
      
      <!-- 文本输入框 -->
      <ion-input 
        *ngIf="currentQuestion.type === 'text'"
        formControlName="{{ currentQuestion.key }}"
        type="text"
        placeholder="请输入答案"
        class="ion-margin-top"
      ></ion-input>

      <!-- 数字输入框 -->
      <ion-input 
        *ngIf="currentQuestion.type === 'number'"
        formControlName="{{ currentQuestion.key }}"
        type="number"
        placeholder="请输入数字"
        class="ion-margin-top"
      ></ion-input>

      <!-- 单选选项组 -->
      <ion-radio-group 
        *ngIf="currentQuestion.options"
        formControlName="{{ currentQuestion.key }}"
        class="ion-margin-top"
      >
        <ion-item *ngFor="let opt of currentQuestion.options">
          <ion-label>{{ opt }}</ion-label>
          <ion-radio slot="start" [value]="opt"></ion-radio>
        </ion-item>
      </ion-radio-group>
    </div>

    <!-- 导航与提交按钮 -->
    <div class="ion-margin-top ion-text-center">
      <ion-button 
        *ngIf="quizService.currentStep > 0"
        (click)="quizService.prevStep()"
        fill="outline"
      >上一题</ion-button>
      
      <ion-button 
        *ngIf="!isLastStep()"
        (click)="quizService.nextStep()"
        [disabled]="!quizService.quizForm.get(currentQuestion.key).valid"
        class="ion-margin-start"
      >下一题</ion-button>

      <ion-button 
        *ngIf="isLastStep()"
        (click)="quizService.submitQuiz()"
        [disabled]="!quizService.quizForm.valid"
        color="success"
        class="ion-margin-start"
      >提交问卷</ion-button>
    </div>

    <!-- 提交后显示答案汇总 -->
    <div *ngIf="quizService.quizForm.valid && isLastStep() && quizService.quizForm.dirty" class="ion-margin-top">
      <h3 class="ion-text-center">你的答案</h3>
      <ion-list>
        <ion-item *ngFor="let q of quizService.questions">
          <ion-label>{{ q.label }}</ion-label>
          <ion-note slot="end">{{ quizService.quizForm.value[q.key] }}</ion-note>
        </ion-item>
      </ion-list>
    </div>
  </form>
</ion-content>

三、跨路由分步表单(多组件)

如果需要通过路由跳转切换问题,用共享服务存储表单状态,每个路由对应一个问题组件。

1. 扩展共享服务(添加路由导航)

// 在QuizFormService中新增
import { Router } from '@angular/router';

constructor(private fb: FormBuilder, private router: Router) { ... }

goToNextQuestion() {
  this.nextStep();
  this.router.navigate([`/question/${this.currentStep + 1}`]);
}

goToPrevQuestion() {
  this.prevStep();
  this.router.navigate([`/question/${this.currentStep + 1}`]);
}

2. 创建单个问题组件

// src/app/question/question.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { QuizFormService } from '../services/quiz-form.service';

@Component({
  selector: 'app-question',
  templateUrl: './question.component.html',
  styleUrls: ['./question.component.scss'],
})
export class QuestionComponent implements OnInit {
  questionIndex: number;
  question: any;

  constructor(
    private route: ActivatedRoute,
    public quizService: QuizFormService
  ) {}

  ngOnInit() {
    this.route.paramMap.subscribe(params => {
      this.questionIndex = +params.get('index') - 1;
      this.quizService.currentStep = this.questionIndex;
      this.question = this.quizService.questions[this.questionIndex];
    });
  }

  isLastQuestion() {
    return this.questionIndex === this.quizService.questions.length - 1;
  }
}

3. 配置路由

// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { QuestionComponent } from './question/question.component';

const routes: Routes = [
  { path: '', redirectTo: '/question/1', pathMatch: 'full' },
  { path: 'question/:index', component: QuestionComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

核心要点

  • 响应式表单:统一管理所有问题的控件,简化验证和数据收集逻辑。
  • 共享服务:跨组件/路由时,通过服务存储表单状态,避免数据丢失。
  • 步骤控制:通过currentStep变量控制当前显示的问题,切换前验证输入有效性。

内容的提问来源于stack exchange,提问作者ALLAN ABU

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 10:14:59