Angular入门:用Signals状态管理和Bootstrap基础样式实现的用户登录注册实例教程


发布者 ourjs  发布时间 1768440483942
关键字 前端 

这套代码实现了一个 Angular 单页应用内的用户登录 / 注册功能模块,核心包含「前端交互层(组件 + 模板)」和「状态管理层(Signals 服务)」。整体基于 Angular 原生能力实现,无第三方状态管理库,用 Signals 替代传统 RxJS 实现响应式,依赖 Bootstrap 快速实现基础样式;

Angular 安装和项目初始化

安装angular

npm install -g @angular/cli

初始化项目

ng new ui --routing=true --style=css

开始调试服务

cd ui
ng serve

添加 Bootstrap

添加 jquery 和 bootstrap

npm add jquery
npm add bootstrap@4.5.3

修改 angular.json 加入样式和脚本引用

"styles": [
  "node_modules/bootstrap/dist/css/bootstrap.min.css",
  "src/styles.css"
],
"scripts": [
  "node_modules/jquery/dist/jquery.min.js",
  "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
],

添加 angular component

mkdir components
cd components
ng generate component navbar

定义 app-navbar

package\ui\src\app\components\navbar\navbar.ts

定义组件和传参

子组件接参:

  1. @Input() 定义要传入的属性。
  2. !确定为非空,不需要初始化
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-navbar',
  imports: [],
  templateUrl: './navbar.html',
  styleUrl: './navbar.css',
})
export class Navbar {
  @Input()
  title!: string
}

定义组件html模板

package\ui\src\app\components\navbar\navbar.html

<div class="sitenav d-flex justify-content-center align-items-center">
    <a class="btn btn-primary btn-home" to="/"></a>
    <h5 class="my-2 brand">{{title}}</h5>
    <a class="btn btn-primary btn-manage" to="/manage"></a>
</div>

添加引用

app.ts 添加引用

package\ui\src\app\app.ts

  1. 引用 Navbar
  2. imports Navbar
import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { Navbar } from './components/navbar/navbar';

@Component({
  selector: 'app-root',
  imports: [
    RouterOutlet,
    Navbar,
  ],
  templateUrl: './app.html',
  styleUrl: './app.css'
})
export class App {
  protected readonly title = signal('ui');
}

app.html 添加引用

package\ui\src\app\app.html

  1. 与之前定义的selector相匹配
  2. 父组件传参通过 title 赋值
<div class="page-welcome">
  <app-navbar title="Book hotel" />
  <router-outlet></router-outlet>
</div>

添加新页面

创建页面组件

ng generate component pages/book

页面结构

src/app/pages/home/
├── home.component.ts       # 组件逻辑
├── home.component.html     # 页面模板(HTML)
├── home.component.css      # 页面样式
└── home.component.spec.ts  # 测试文件(新手可暂时忽略)

修改页面模板

定义参数

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-book',
  imports: [],
  templateUrl: './book.html',
  styleUrl: './book.css',
})
export class Book implements OnInit {
  id = ""
  name = ""
  telephone = ""
  size = 0

  constructor(private route: ActivatedRoute) {
    
  }

  ngOnInit(): void {
    const idInRoute = this.route.snapshot.paramMap.get('id')
    if (idInRoute !== null) {
      this.id = idInRoute
    }

    //订阅参数的变化
    this.route.queryParamMap.subscribe(params => {
      const idInQuery = this.route.snapshot.queryParamMap.get('id')
      if (idInQuery !== null) {
        this.id = idInQuery
      }
    })
  }
}

修改页面模板

<div class="reservation">
    <div class="card">
        <div class="card-img-top">Book ID: {{id}}</div>
        <div class="card-body">
            <h5 class="card-title">My Reservation</h5>
            <p class="card-text"></p>
        </div>
        <ul class="list-group list-group-flush">
            <li class="list-group-item">Name: {{name}}</li>
            <li class="list-group-item">Telephone: {{telephone}}</li>
            <li class="list-group-item">Table Size: {{size}}</li>
        </ul>
        <div class="card-body card-btns">
            <a onClick={onUpdate} class="btn btn-primary card-link">Update Reservation</a>
            <a onClick={onCancel} class="btn btn-default card-link">Cancel</a>
        </div>
    </div>
</div>

添加页面路由

package\ui\src\app\app.routes.ts

  1. 默认进入 Sign 登录页面
  2. book/:id 为带参路由
import { Routes } from '@angular/router';
import { Book } from './pages/book/book';
import { Sign } from './pages/sign/sign';

export const routes: Routes = [
  {
    path: '',
    component: Sign
  },
  {
    path: 'book/:id',
    component: Book
  },
];

Angular Signals 用户状态管理实例

Angular 16+ 推出的 Signals 是官方原生响应式工具,无需额外安装库,可直接作为轻量状态管理方案,具体原生、轻量、性能优的特点。

ng generate service state/user.state.service

这段代码是一个 Angular 全局状态服务(UserStateService),基于 Signals 管理用户相关状态,核心实现了用户注册(signUp)、登录(signIn)、根据用户名查询用户(findUser) 三个功能,状态全部存储在内存中(无持久化)。

  • Injectable:Angular 服务的核心装饰器,标记该类可被依赖注入;
  • signal:Angular Signals 核心 API,用于创建响应式状态;
  • User 类型:定义用户数据结构,包含可选的 username(用户名)和 password(密码)字段(? 表示可选)。
import { Injectable, signal } from '@angular/core';

export type User = {
  username?: string
  password?: string
}

@Injectable({
  providedIn: 'root',
})
export class UserStateService {
  // current login user
  user = signal<User>({})
  // all users
  list = signal<User[]>([])

  async findUser(username?: string) {
    if (username === undefined) {
      return
    }

    return this.list().find(
      (user) => user.username === username
    )
  }

  async signUp(user: User) {
    if (await this.findUser(user.username)) {
      throw new Error("User exist")
    } else {
      this.list().push(user)
      this.user.set(user)
    }
  }

  async signIn(loginUser: User) {
    const user = await this.findUser(loginUser.username)
    if (user === undefined) {
      throw new Error("User not exist")
    }
    if (user.password !== loginUser.password) {
      throw new Error("Password not right")
    }

    this.user.set(user)
  }
}

这是一个 Angular 登录 / 注册组件(Sign 组件),核心实现:

  • 从页面输入框获取用户名和密码;
  • 调用 UserStateService 完成注册(onSingUp)、登录(onSignIn);
  • 登录成功后跳转到 /book/[用户名] 路径;
  • 注册 / 登录失败时,在页面展示错误提示信息。
import { Component } from '@angular/core';
import { User, UserStateService } from '../../state/user.state.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-sign',
  imports: [ ],
  templateUrl: './sign.html',
  styleUrl: './sign.css',
})
export class Sign {
  message = ""

  constructor(
    private router: Router,
    private userStateService: UserStateService,
  ) {

  }

  getUser(): User {
    return {
      username: document.querySelector<HTMLInputElement>("#username")?.value,
      password: document.querySelector<HTMLInputElement>("#password")?.value
    }
  }

  onLogin() {
    this.router.navigate(['/book', this.userStateService.user().username])
  }

  async onSingUp() {
    try {
      await this.userStateService.signUp(this.getUser())
      this.message = "User signed up"
    } catch(e: any) {
      this.message = e.toString()
    }
  }

  async onSignIn() {
    try {
      await this.userStateService.signIn(this.getUser())
      this.onLogin()
    } catch(e: any) {
      this.message = e.toString()
    }
  }
}

这是一个登录 / 注册的表单页面模板,包含用户名、密码输入框和注册 / 登录按钮,点击按钮会触发组件对应的方法,页面还会根据组件的 message 属性展示提示信息

<div class="datalist">
    <form>
        <h3>{{ message || "Please Login" }}</h3>
        <div class="form-group">
            <label htmlFor="username">Username</label>
            <input type="text" class="form-control" id="username" value="admin" />
        </div>
        <div class="form-group">
            <label htmlFor="password">Password</label>
            <input type="password" class="form-control" id="password" value="123456" />
        </div>
        <div class="submit-wrap">
            <button (click)="onSingUp()" class="btn btn-primary btn-submit btn-lg" type="button">Sign Up</button>
            <button (click)="onSignIn()" class="btn btn-primary btn-submit btn-lg ml-3" type="button">Sign In</button>
        </div>
    </form>
</div>








 热门文章 - 分享最多
  1. 用Gitea搭建免费Git服务器自定义Actions配置CI/CD自动化部署和测试流水线
  2. FastAPI+SQLModel+PostgreSQL+React+Vite全栈项目文件结构说明环境搭建与初始化指南
  3. React结合vite使用vue3,在纯typescript的react hooks中使用vue
  4. valtio基于Proxy代理比redux\zustand更简洁的react状态管理库
  5. React Native为http网络请求添加timeout超时异常处理: 用XMLHttpRequest替换fetch发送的区别
  6. React Native使用fetch发送http登陆验证请求失败:无法读取set-cookie并显示network request failed
  7. 克服Redux的缺点在React/Native中使用消息队列,pubsub-js更加简洁的组件间通信和状态传递方法
  8. Springboot+Gradle+Mysql+Jpa最简单实例教程
  9. SpringBoot+Spring6入门指南: 使用命令行快速搭建restful web api模板
  10. 如何通过 winax 的 ActiveXObject 或 Excel.Application 往 excel 中插入一张图片

 相关阅读
  1. puppeteer等自动化测试框架如何判断CSS动画结束animation end
  2. React Hooks入门教程九:在React中集成使用Vue实现数据双向绑定,手动配置Webpack和Babel
  3. JavaScript和node.js内存泄露的原因和避免方法及Chrome调试工具使用教程
  4. JavaScript设置对象属性只读不可修改、不可枚举、不可删除:Object.defineProperty
  5. CSS教程:图片使用混合模式和颜色叠加filter滤镜,改变PNG图标颜色
  6. CSS教程:如何设置自动显示隐藏scrollbar滚动条,自定义外观样式/宽度,附demo示例大全
  7. JavaScript判断字符串是否为数字类型:Number.isInteger、isNaN、正则表达式比较
  8. webpack前端项目调试环境安装入门:webpack.config.js禁用UglifyJs只合并JavaScript不压缩混淆代码
  9. 用CSS实现分页符,控制Web网页打印时自动强制分页:page-break-after教程
  10. SVG矢量图视窗viewBox,嵌套HTML综合实例:建立用户自定义相对坐标系统

  开源的 OurJS
OurJS开源博客已经迁移到 OnceOA 平台。

  关注我们
扫一扫即可关注我们:
OnceJS

OnceOA