Heim >Web-Frontend >js-Tutorial >Erstellen einer URL-Shortener-App mit Angular und Tailwind CSS

Erstellen einer URL-Shortener-App mit Angular und Tailwind CSS

WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWB
WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOriginal
2024-07-26 18:53:311180Durchsuche

Building a URL Shortener App with Angular and Tailwind CSS

In diesem Blog führen wir Sie durch den Prozess der Erstellung einer URL-Shortener-Anwendung mit Angular für das Frontend und Tailwind CSS für das Styling. Ein URL-Shortener ist ein praktisches Tool, das lange URLs in kürzere, besser verwaltbare Links umwandelt. Dieses Projekt wird Ihnen helfen zu verstehen, wie Sie mithilfe moderner Webentwicklungstechnologien eine funktionale und ästhetisch ansprechende Webanwendung erstellen.

Voraussetzungen

Um diesem Tutorial folgen zu können, sollten Sie über grundlegende Kenntnisse von Angular und etwas Vertrautheit mit Tailwind CSS verfügen. Stellen Sie sicher, dass Node.js und Angular CLI auf Ihrem Computer installiert sind.

Projekt-Setup

1. Erstellen eines neuen Angular-Projekts

Erstellen Sie zunächst ein neues Angular-Projekt, indem Sie den folgenden Befehl in Ihrem Terminal ausführen:

ng new url-shortener-app
cd url-shortener-app

2. Tailwind CSS installieren

Als nächstes richten Sie Tailwind CSS in Ihrem Angular-Projekt ein. Installieren Sie Tailwind CSS und seine Abhängigkeiten über npm:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init

Konfigurieren Sie Tailwind CSS, indem Sie die Datei tailwind.config.js aktualisieren:

module.exports = {
  content: [
    "./src/**/*.{html,ts}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Fügen Sie die Tailwind-Anweisungen zu Ihrer Datei src/styles.scss hinzu:

@tailwind base;
@tailwind components;
@tailwind utilities;

Erstellen des URL-Shorteners

3. Erstellen des URL-Modells

Erstellen Sie ein URL-Modell, um die Struktur der URL-Daten zu definieren. Fügen Sie eine neue Datei src/app/models/url.model.ts:
hinzu

export type Urls = Url[];

export interface Url {
  _id: string;
  originalUrl: string;
  shortUrl: string;
  clicks: number;
  expirationDate: string;
  createdAt: string;
  __v: number;
}

4. Einrichten des URL-Dienstes

Erstellen Sie einen Dienst zur Verarbeitung von API-Aufrufen im Zusammenhang mit der URL-Verkürzung. Fügen Sie eine neue Datei src/app/services/url.service.ts:
hinzu

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. Erstellen der Shorten-URL-Komponente

Generieren Sie eine neue Komponente zum Kürzen von URLs:

ng generate component shorten

Aktualisieren Sie den HTML-Code der Komponente (src/app/shorten/shorten.component.html) wie unten gezeigt:

<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. Hinzufügen von Logik zur Komponente

Aktualisieren Sie die TypeScript-Datei der Komponente (src/app/shorten/shorten.component.ts), um Formularübermittlungen und API-Interaktionen zu verarbeiten:

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. Aktualisieren Sie die HTML-Datei der App-Komponente (src/app/app.component.html).

<router-outlet></router-outlet>

8. Aktualisieren Sie die App-Konfigurationsdatei (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. Aktualisieren Sie die App-Routen-Datei (src/app/app.routes.ts).

import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: '',
    loadComponent: () =>
      import('./shorten/shorten.component').then((m) => m.ShortenComponent),
  },
];

Abschluss

Sie haben erfolgreich eine URL-Shortener-Anwendung mit Angular und Tailwind CSS erstellt. Dieses Projekt zeigt, wie man moderne Frontend-Technologien integriert, um eine funktionale und stilvolle Webanwendung zu erstellen. Mit den leistungsstarken Funktionen von Angular und dem Utility-First-Ansatz von Tailwind CSS können Sie problemlos reaktionsfähige und effiziente Webanwendungen erstellen.

Sie können diese Anwendung gerne erweitern, indem Sie Funktionen wie Benutzerauthentifizierung usw. hinzufügen. Viel Spaß beim Codieren!

Den Code erkunden

Besuchen Sie das GitHub-Repository, um den Code im Detail zu erkunden.


Das obige ist der detaillierte Inhalt vonErstellen einer URL-Shortener-App mit Angular und Tailwind CSS. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn