使用 Angular 和 Tailwind CSS 构建 URL 缩短应用程序
来源:dev.to
时间:2024-07-26 18:24:49 157浏览 收藏
一分耕耘,一分收获!既然打开了这篇文章《使用 Angular 和 Tailwind CSS 构建 URL 缩短应用程序》,就坚持看下去吧!文中内容包含等等知识点...希望你能在阅读本文后,能真真实实学到知识或者帮你解决心中的疑惑,也欢迎大佬或者新人朋友们多留言评论,多给建议!谢谢!
在本博客中,我们将引导您完成使用 angular 作为前端并使用 tailwind css 进行样式创建 url 缩短器应用程序的过程。 url 缩短器是一个方便的工具,可以将长 url 转换为更短、更易于管理的链接。该项目将帮助您了解如何使用现代 web 开发技术构建功能齐全且美观的 web 应用程序。
先决条件
要学习本教程,您应该对 angular 有基本的了解,并对 tailwind css 有一定的了解。确保您的计算机上安装了 node.js 和 angular cli。
项目设置
1. 创建一个新的 angular 项目
首先,通过在终端中运行以下命令来创建一个新的 angular 项目:
ng new url-shortener-app cd url-shortener-app
2. 安装 tailwind css
接下来,在您的 angular 项目中设置 tailwind css。通过 npm 安装 tailwind css 及其依赖项:
npm install -d tailwindcss postcss autoprefixer npx tailwindcss init
通过更新 tailwind.config.js 文件来配置 tailwind css:
module.exports = { content: [ "./src/**/*.{html,ts}", ], theme: { extend: {}, }, plugins: [], }
将 tailwind 指令添加到您的 src/styles.scss 文件中:
@tailwind base; @tailwind components; @tailwind utilities;
构建 url 缩短器
3. 创建 url 模型
创建 url 模型来定义 url 数据的结构。添加新文件 src/app/models/url.model.ts:
export type urls = url[]; export interface url { _id: string; originalurl: string; shorturl: string; clicks: number; expirationdate: string; createdat: string; __v: number; }
4. 设置 url 服务
创建一个服务来处理与 url 缩短相关的 api 调用。添加新文件 src/app/services/url.service.ts:
import { httpclient } from '@angular/common/http'; import { injectable } from '@angular/core'; import { observable } from 'rxjs'; import { url, urls } from '../models/url.model'; import { environment } from '../../environments/environment'; @injectable({ providedin: 'root', }) export class urlservice { private apiurl = environment.apiurl; constructor(private http: httpclient) {} shortenurl(originalurl: string): observable<url> { return this.http.post<url>(`${this.apiurl}/shorten`, { originalurl }); } getallurls(): observable<urls> { return this.http.get<urls>(`${this.apiurl}/urls`); } getdetails(id: string): observable<url> { return this.http.get<url>(`${this.apiurl}/details/${id}`); } deleteurl(id: string): observable<url> { return this.http.delete<url>(`${this.apiurl}/delete/${id}`); } }
5. 创建缩短 url 组件
生成一个用于缩短 url 的新组件:
ng generate component shorten
更新组件的 html (src/app/shorten/shorten.component.html) 如下所示:
<div class="max-w-md mx-auto p-4 shadow-lg rounded-lg mt-4"> <h2 class="text-2xl font-bold mb-2">url shortener</h2> <form [formgroup]="urlform" (ngsubmit)="shortenurl()"> <div class="flex items-center mb-2"> <input class="flex-1 p-2 border border-gray-300 rounded mr-4" formcontrolname="originalurl" placeholder="enter your url" required /> <button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" type="submit"> shorten </button> </div> @if (urlform.get('originalurl')?.invalid && (urlform.get('originalurl')?.dirty || urlform.get('originalurl')?.touched)) { <div class="text-red-500" role="alert" aria-live="assertive"> @if (urlform.get('originalurl')?.errors?.['required']) { url is required. } @if (urlform.get('originalurl')?.errors?.['pattern']) { invalid url format. please enter a valid url starting with http:// or https://. } </div> } </form> @if (errormsg) { <div class="p-4 bg-red-100 rounded mt-4"> <p class="text-red-500">{{ errormsg }}</p> </div> } @if (shorturl) { <div class="p-4 bg-green-100 rounded"> <p>shortened url: <a class="text-blue-500 hover:text-blue-600" [href]="redirecturl + shorturl" target="_blank">{{ shorturl }}</a> <button class="ml-2 px-2 py-1 bg-gray-200 text-gray-800 border border-slate-950 rounded hover:bg-gray-300" (click)="copyurl(redirecturl + shorturl)">copy</button> @if (copymessage) { <span class="text-green ml-2">{{ copymessage }}</span> } </p> </div> } </div> <div class="max-w-md mx-auto mt-4 p-2"> <h2 class="text-2xl font-bold mb-4">all urls</h2> @if (isloading) { <div class="max-w-md mx-auto p-4 shadow-lg rounded-lg"> <div class="text-center p-4"> loading... </div> </div> } @else if (error) { <div class="max-w-md mx-auto p-4 shadow-lg rounded-lg"> <div class="text-center p-4"> <p class="text-red-500">{{ error }}</p> </div> </div> } @else { @if (urls.length > 0 && !isloading && !error) { <ul> @for (url of urls; track $index) { <li class="p-2 border border-gray-300 rounded mb-2"> <div class="flex justify-between items-center"> <div> url: <a class="text-blue-500 hover:text-blue-600" [href]="redirecturl + url.shorturl" target="_blank">{{ url.shorturl }}</a> </div> <div class="flex justify-between items-center"> <button class="px-2 py-1 bg-blue-200 text-blue-800 rounded hover:bg-blue-300" (click)="showdetails(url.shorturl)">details</button> <button class="ml-2 px-2 py-1 bg-gray-200 text-gray-800 rounded hover:bg-gray-300" (click)="copylisturl(redirecturl + url.shorturl, $index)">{{ copyindex === $index ? 'copied' : 'copy' }}</button> <button class="ml-2 px-2 py-1 bg-red-200 text-red-800 rounded hover:bg-red-300" (click)="preparedelete(url.shorturl)">delete</button> </div> </div> </li> } </ul> } @else { <div class="max-w-md mx-auto p-4 shadow-lg rounded-lg"> <div class="text-center p-4"> no urls found. </div> </div> } } </div> @if (showdeletemodal) { <div class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"> <div class="bg-white p-4 rounded shadow-lg"> <h3 class="text-xl font-bold mb-2">confirm deletion</h3> <p class="mb-4">are you sure you want to delete this url?</p> <button class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600" (click)="confirmdelete()">yes, delete</button> <button class="px-4 py-2 bg-gray-300 text-gray-800 rounded hover:bg-gray-400 ml-2" (click)="showdeletemodal = false">cancel</button> </div> </div> } @if (showdetailsmodal) { <div class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"> <div class="bg-white p-4 rounded shadow-lg"> <h3 class="text-xl font-bold mb-2">url details</h3> @if (isloading) { <p class="mb-4">loading...</p> } @else { <p class="mb-4">short url: <a class="text-blue-500 hover:text-blue-600" [href]="redirecturl + selectedurl.shorturl" target="_blank">{{ selectedurl.shorturl }}</a></p> <p class="mb-4">original url: <a class="text-blue-500 hover:text-blue-600" [href]="selectedurl.originalurl" target="_blank">{{ selectedurl.originalurl }}</a></p> <p class="mb-4">clicks: <span class="text-green-500">{{ selectedurl.clicks }}</span></p> <p class="mb-4">created at: {{ selectedurl.createdat | date: 'medium' }}</p> <p class="mb-4">expires at: {{ selectedurl.expirationdate | date: 'medium' }}</p> <button class="px-4 py-2 bg-gray-300 text-gray-800 rounded hover:bg-gray-400" (click)="showdetailsmodal = false">close</button> } </div> </div> }
6. 向组件添加逻辑
更新组件的 typescript 文件(src/app/shorten/shorten.component.ts)以处理表单提交和 api 交互:
import { component, inject, oninit } from '@angular/core'; import { urlservice } from '../services/url.service'; import { formcontrol, formgroup, reactiveformsmodule, validators, } from '@angular/forms'; import { url } from '../models/url.model'; import { environment } from '../../environments/environment'; import { datepipe } from '@angular/common'; import { subject, takeuntil } from 'rxjs'; @component({ selector: 'app-shorten', standalone: true, imports: [datepipe, reactiveformsmodule], templateurl: './shorten.component.html', styleurl: './shorten.component.scss', }) export class shortencomponent implements oninit { shorturl: string = ''; redirecturl = environment.apiurl + '/'; copymessage: string = ''; copylistmessage: string = ''; urls: url[] = []; showdeletemodal = false; showdetailsmodal = false; urltodelete = ''; copyindex: number = -1; selectedurl: url = {} as url; isloading = false; isloading = false; error: string = ''; errormsg: string = ''; urlform: formgroup = new formgroup({}); private unsubscribe$: subject<void> = new subject<void>(); urlservice = inject(urlservice); ngoninit() { this.urlform = new formgroup({ originalurl: new formcontrol('', [ validators.required, validators.pattern('^(http|https)://.*$'), ]), }); this.getallurls(); } shortenurl() { if (this.urlform.valid) { this.urlservice.shortenurl(this.urlform.value.originalurl).pipe(takeuntil(this.unsubscribe$)).subscribe({ next: (response) => { // console.log('shortened url: ', response); this.shorturl = response.shorturl; this.getallurls(); }, error: (error) => { console.error('error shortening url: ', error); this.errormsg = error?.error?.message || 'an error occurred!'; }, }); } } getallurls() { this.isloading = true; this.urlservice.getallurls().pipe(takeuntil(this.unsubscribe$)).subscribe({ next: (response) => { // console.log('all urls: ', response); this.urls = response; this.isloading = false; }, error: (error) => { console.error('error getting all urls: ', error); this.isloading = false; this.error = error?.error?.message || 'an error occurred!'; }, }); } showdetails(id: string) { this.showdetailsmodal = true; this.getdetails(id); } getdetails(id: string) { this.isloading = true; this.urlservice.getdetails(id).subscribe({ next: (response) => { // console.log('url details: ', response); this.selectedurl = response; this.isloading = false; }, error: (error) => { console.error('error getting url details: ', error); this.error = error?.error?.message || 'an error occurred!'; }, }); } copyurl(url: string) { navigator.clipboard .writetext(url) .then(() => { // optional: display a message or perform an action after successful copy console.log('url copied to clipboard!'); this.copymessage = 'copied!'; settimeout(() => { this.copymessage = ''; }, 2000); }) .catch((err) => { console.error('failed to copy url: ', err); this.copymessage = 'failed to copy url'; }); } copylisturl(url: string, index: number) { navigator.clipboard .writetext(url) .then(() => { // optional: display a message or perform an action after successful copy console.log('url copied to clipboard!'); this.copylistmessage = 'copied!'; this.copyindex = index; settimeout(() => { this.copylistmessage = ''; this.copyindex = -1; }, 2000); }) .catch((err) => { console.error('failed to copy url: ', err); this.copylistmessage = 'failed to copy url'; }); } preparedelete(url: string) { this.urltodelete = url; this.showdeletemodal = true; } confirmdelete() { // close the modal this.showdeletemodal = false; // delete the url this.deleteurl(this.urltodelete); } deleteurl(id: string) { this.urlservice.deleteurl(id).subscribe({ next: (response) => { // console.log('deleted url: ', response); this.getallurls(); }, error: (error) => { console.error('error deleting url: ', error); this.error = error?.error?.message || 'an error occurred!'; }, }); } ngondestroy() { this.unsubscribe$.next(); this.unsubscribe$.complete(); } }
7.更新应用程序组件的html文件(src/app/app.component.html)
<router-outlet></router-outlet>
8.更新应用程序配置文件(src/app/app.config.ts)
import { applicationconfig, providezonechangedetection } from '@angular/core'; import { providerouter } from '@angular/router'; import { routes } from './app.routes'; import { providehttpclient } from '@angular/common/http'; export const appconfig: applicationconfig = { providers: [ providezonechangedetection({ eventcoalescing: true }), providerouter(routes), providehttpclient(), ], };
9.更新应用程序路由文件(src/app/app.routes.ts)
import { Routes } from '@angular/router'; export const routes: Routes = [ { path: '', loadComponent: () => import('./shorten/shorten.component').then((m) => m.ShortenComponent), }, ];
结论
您已经使用 angular 和 tailwind css 成功构建了 url 缩短器应用程序。该项目演示了如何集成现代前端技术来创建功能强大且时尚的 web 应用程序。借助 angular 的强大功能和 tailwind css 实用程序优先的方法,您可以轻松构建响应灵敏且高效的 web 应用程序。
请随意通过添加用户身份验证等功能来扩展此应用程序。祝您编码愉快!
探索代码
访问 github 存储库以详细探索代码。
今天带大家了解了的相关知识,希望对你有所帮助;关于文章的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
497 收藏
-
285 收藏
-
238 收藏
-
291 收藏
-
309 收藏
-
393 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 541次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 506次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习