在本博客中,我们将引导您完成使用 angular 作为前端并使用 tailwind css 进行样式创建 url 缩短器应用程序的过程。 url 缩短器是一个方便的工具,可以将长 url 转换为更短、更易于管理的链接。该项目将帮助您了解如何使用现代 web 开发技术构建功能齐全且美观的 web 应用程序。
要学习本教程,您应该对 angular 有基本的了解,并对 tailwind css 有一定的了解。确保您的计算机上安装了 node.js 和 angular cli。
首先,通过在终端中运行以下命令来创建一个新的 angular 项目:
ng new url-shortener-app cd url-shortener-app
接下来,在您的 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 模型来定义 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; }
创建一个服务来处理与 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}`); } } </url></url></url></url></urls></urls></url></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> <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" 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" 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" 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">details</button> <button class="ml-2 px-2 py-1 bg-gray-200 text-gray-800 rounded hover:bg-gray-300" url.shorturl>{{ copyindex === $index ? 'copied' : 'copy' }}</button> <button class="ml-2 px-2 py-1 bg-red-200 text-red-800 rounded hover:bg-red-300">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">yes, delete</button> <button class="px-4 py-2 bg-gray-300 text-gray-800 rounded hover:bg-gray-400 ml-2" 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" selectedurl.shorturl target="_blank">{{ selectedurl.shorturl }}</a></p> <p class="mb-4">original url: <a class="text-blue-500 hover:text-blue-600" 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" false>close</button> } </div> </div> }
更新组件的 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(); } } </void></void>
<router-outlet></router-outlet>
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(), ], };
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 存储库以详细探索代码。