Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

StackPuz
StackPuz

Posted on • Originally published atblog.stackpuz.com on

Building an Angular CRUD App with a Laravel API

Angular CRUD App with a Laravel API

Mastering CRUD operations is essential for developing robust web applications. This article will guide you step by step in building a CRUD app using Angular for the client side and a Laravel API for the server side, demonstrating the powerful synergy between these technologies.

Prerequisites

  • Node.js
  • Composer
  • PHP 8.2
  • MySQL

Setup Angular project

Install Angular 18 and create a new project with the following command.

npm install -g @angular/cli@18.0.0ng new view --minimal --routing --style css --no-standalone --ssr=false
Enter fullscreen modeExit fullscreen mode

Angular project structure

└─ src   ├─ app   │  ├─ app-routing.module.ts   │  ├─ app.component.ts   │  ├─ app.interceptor.ts   │  ├─ app.module.ts   │  └─ components   │     └─ product   │        ├─ Create.component.ts   │        ├─ Delete.component.ts   │        ├─ Detail.component.ts   │        ├─ Edit.component.ts   │        ├─ Index.component.ts   │        └─ Product.service.ts   ├─ index.html   ├─ main.ts   └─ styles.css
Enter fullscreen modeExit fullscreen mode

*This project structure will display only the files and folders that we plan to create or modify.

Angular Project files

main.ts

import{enableProdMode}from'@angular/core'import{platformBrowserDynamic}from'@angular/platform-browser-dynamic'import{AppModule}from'./app/app.module'platformBrowserDynamic().bootstrapModule(AppModule).catch(e=>console.error(e))
Enter fullscreen modeExit fullscreen mode

Thismain.ts file initializes an Angular application by bootstrapping theAppModule using theplatformBrowserDynamic function. It sets up the application to run in the browser and handles any errors that occur during the bootstrapping process.

app.module.ts

import{NgModule}from'@angular/core'import{BrowserModule}from'@angular/platform-browser'import{FormsModule}from'@angular/forms'import{HttpClientModule,HTTP_INTERCEPTORS}from'@angular/common/http'import{AppRoutingModule}from'./app-routing.module'import{AppComponent}from'./app.component'import{AppInterceptor}from'./app.interceptor'import{ProductIndex}from'./components/product/Index.component'import{ProductCreate}from'./components/product/Create.component'import{ProductDetail}from'./components/product/Detail.component'import{ProductEdit}from'./components/product/Edit.component'import{ProductDelete}from'./components/product/Delete.component'@NgModule({declarations:[AppComponent,ProductIndex,ProductCreate,ProductDetail,ProductEdit,ProductDelete,],imports:[BrowserModule,AppRoutingModule,FormsModule,HttpClientModule],providers:[{provide:HTTP_INTERCEPTORS,useClass:AppInterceptor,multi:true}],bootstrap:[AppComponent]})exportclassAppModule{}
Enter fullscreen modeExit fullscreen mode

TheAppModule is the main module of an Angular application. It imports core Angular modules and sets up routing withAppRoutingModule. The module declares various product-related components. It also registersAppInterceptor as an HTTP interceptor. TheAppComponent is set as the bootstrap component, making it the entry point of the application.

app.component.ts

import{Component}from'@angular/core'@Component({selector:'app-root',template:`<router-outlet></router-outlet>`})exportclassAppComponent{}
Enter fullscreen modeExit fullscreen mode

Theapp.component.ts file defines the root component,AppComponent, which uses the<router-outlet> directive in its template to display routed views. The component is identified by theapp-root selector and serves as the entry point for the Angular application.

app.interceptor.ts

import{Injectable}from'@angular/core';import{HttpInterceptor}from'@angular/common/http';import{HttpRequest,HttpErrorResponse}from'@angular/common/http'import{Observable,throwError}from'rxjs'import{HttpHandler}from'@angular/common/http'import{HttpEvent}from'@angular/common/http'@Injectable({providedIn:'root'})exportclassAppInterceptorimplementsHttpInterceptor{baseURL='http://localhost:8000/api'intercept(request:HttpRequest<any>,next:HttpHandler):Observable<HttpEvent<any>>{returnnext.handle(request.clone({url:this.baseURL+request.url,}))}}
Enter fullscreen modeExit fullscreen mode

TheAppInterceptor class is an Angular HTTP interceptor that appends a configurablebaseURL to all outgoing HTTP request URLs before they are sent to the server. This allows the application to centralize and easily manage the base API endpoint.

app-routing.module.ts

import{NgModule}from'@angular/core'import{RouterModule,Routes}from'@angular/router'import{ProductIndex}from'./components/product/Index.component'import{ProductCreate}from'./components/product/Create.component'import{ProductDetail}from'./components/product/Detail.component'import{ProductEdit}from'./components/product/Edit.component'import{ProductDelete}from'./components/product/Delete.component'constroutes:Routes=[{path:'',redirectTo:'product',pathMatch:'full'},{path:'product',component:ProductIndex},{path:'product/create',component:ProductCreate},{path:'product/:id',component:ProductDetail},{path:'product/edit/:id',component:ProductEdit},{path:'product/delete/:id',component:ProductDelete}]@NgModule({imports:[RouterModule.forRoot(routes)],exports:[RouterModule]})exportclassAppRoutingModule{}
Enter fullscreen modeExit fullscreen mode

TheAppRoutingModule sets up routing for an Angular application, including product-related paths for listing, creating, viewing, editing, and deleting products. It also includes a route that redirects from the root path "/" to the product listing page "/product".

Create.component.ts

import{Component}from'@angular/core'import{ActivatedRoute,Router}from'@angular/router'import{ProductService}from'./Product.service'@Component({selector:'product-create',template:`    <div>      <div>        <div>          <form ngNativeValidate method="post" (submit)="create()">            <div>              <div>                <label for="product_name">Name</label>                <input name="name" [(ngModel)]="product.name" maxlength="50" />                <span *ngIf="errors.name">{{errors.name}}</span>              </div>              <div>                <label for="product_price">Price</label>                <input name="price" [(ngModel)]="product.price" type="number" />                <span *ngIf="errors.price">{{errors.price}}</span>              </div>              <div>                <a routerLink="/product">Cancel</a>                <button>Submit</button>              </div>            </div>          </form>        </div>      </div>    </div>`})exportclassProductCreate{product?:any={}errors?:any={}constructor(privaterouter:Router,privateroute:ActivatedRoute,privateProductService:ProductService){}create(){this.ProductService.create(this.product).subscribe(()=>{this.router.navigateByUrl('/product')},(e)=>{alert(e.error)})}}
Enter fullscreen modeExit fullscreen mode

TheProductCreate component provides a form for creating a new product, binding input fields for name and price to aproduct object. On submission, it callsProductService to create the product and navigates back to the product list. Validation errors are displayed next to the corresponding fields, and any creation errors trigger an alert.

Delete.component.ts

import{Component}from'@angular/core'import{ActivatedRoute,Router}from'@angular/router'import{ProductService}from'./Product.service'@Component({selector:'product-delete',template:`    <div>      <div>        <div>          <form ngNativeValidate method="post" (submit)="this.delete()">            <div>              <div>                <label for="product_id">Id</label>                <input readonly name="id" value="{{product.id}}" type="number" required />              </div>              <div>                <label for="product_name">Name</label>                <input readonly name="name" value="{{product.name}}" maxlength="50" />              </div>              <div>                <label for="product_price">Price</label>                <input readonly name="price" value="{{product.price}}" type="number" />              </div>              <div>                <a routerLink="/product">Cancel</a>                <button>Delete</button>              </div>            </div>          </form>        </div>      </div>    </div>`})exportclassProductDelete{product?:any={}constructor(privaterouter:Router,privateroute:ActivatedRoute,privateProductService:ProductService){}ngOnInit(){this.get()}get(){returnthis.ProductService.delete(this.route.snapshot.params['id']).subscribe(data=>{this.product=data},e=>{alert(e.error)})}delete(){this.ProductService.delete(this.route.snapshot.params['id'],this.product).subscribe(()=>{this.router.navigateByUrl('/product')},(e)=>{alert(e.error)})}}
Enter fullscreen modeExit fullscreen mode

TheProductDelete component inDelete.component.ts is an Angular component that handles the deletion of a product. It displays a form with read-only fields showing the product's details (ID, name, and price). When the component initializes, it fetches the product details using the product ID from the route. On form submission, thedelete() method callsProductService to delete the product and then redirects to the product list. If there's an error during deletion, an alert is shown.

Detail.component.ts

import{Component}from'@angular/core'import{ActivatedRoute}from'@angular/router'import{ProductService}from'./Product.service'@Component({selector:'product-detail',template:`    <div>      <div>        <div>          <form ngNativeValidate method="post">            <div>              <div>                <label for="product_id">Id</label>                <input readonly name="id" value="{{product.id}}" type="number" required />              </div>              <div>                <label for="product_name">Name</label>                <input readonly name="name" value="{{product.name}}" maxlength="50" />              </div>              <div>                <label for="product_price">Price</label>                <input readonly name="price" value="{{product.price}}" type="number" />              </div>              <div>                <a routerLink="/product">Back</a>                <a routerLink="/product/edit/{{product.id}}">Edit</a>              </div>            </div>          </form>        </div>      </div>    </div>`})exportclassProductDetail{product?:any={}constructor(privateroute:ActivatedRoute,privateProductService:ProductService){}ngOnInit(){this.get()}get(){returnthis.ProductService.get(this.route.snapshot.params['id']).subscribe(data=>{this.product=data},e=>{alert(e.error)})}}
Enter fullscreen modeExit fullscreen mode

TheProductDetail component displays the details of a specific product. It retrieves the product information based on the ID from the route and shows the product's ID, name, and price in read-only fields. The component provides "Back" and "Edit" buttons for navigation. The product details are fetched and displayed when the component initializes.

Edit.component.ts

import{Component}from'@angular/core'import{ActivatedRoute,Router}from'@angular/router'import{ProductService}from'./Product.service'@Component({selector:'product-edit',template:`    <div>      <div>        <div>          <form ngNativeValidate method="post" (submit)="edit()">            <div>              <div>                <label for="product_id">Id</label>                <input readonly name="id" [(ngModel)]="product.id" type="number" required />                <span *ngIf="errors.id">{{errors.id}}</span>              </div>              <div>                <label for="product_name">Name</label>                <input name="name" [(ngModel)]="product.name" maxlength="50" />                <span *ngIf="errors.name">{{errors.name}}</span>              </div>              <div>                <label for="product_price">Price</label>                <input name="price" [(ngModel)]="product.price" type="number" />                <span *ngIf="errors.price">{{errors.price}}</span>              </div>              <div>                <a routerLink="/product">Cancel</a>                <button>Submit</button>              </div>            </div>          </form>        </div>      </div>    </div>`})exportclassProductEdit{product?:any={}errors?:any={}constructor(privaterouter:Router,privateroute:ActivatedRoute,privateProductService:ProductService){}ngOnInit(){this.get()}get(){returnthis.ProductService.edit(this.route.snapshot.params['id']).subscribe(data=>{this.product=data},e=>{alert(e.error)})}edit(){this.ProductService.edit(this.route.snapshot.params['id'],this.product).subscribe(()=>{this.router.navigateByUrl('/product')},(e)=>{alert(e.error)})}}
Enter fullscreen modeExit fullscreen mode

TheProductEdit component allows users to edit an existing product. It retrieves the product details using the product ID from the route and displays them in a form with editable fields for name and price. On form submission, it updates the product viaProductService and navigates back to the product list. Any errors during fetching or updating are shown as alerts, and validation errors are displayed next to the relevant fields.

Index.component.ts

import{Component}from'@angular/core'import{Router,NavigationEnd}from'@angular/router'import{ProductService}from'./Product.service'@Component({selector:'product-index',template:`    <div>      <div>        <div>          <table>            <thead>              <tr>                <th>Id</th>                <th>Name</th>                <th>Price</th>                <th>Actions</th>              </tr>            </thead>            <tbody>              <tr *ngFor="let product of products">                <td>{{product.id}}</td>                <td>{{product.name}}</td>                <td>{{product.price}}</td>                <td>                  <a routerLink="/product/{{product.id}}" title="View"><i></i></a>                  <a routerLink="/product/edit/{{product.id}}" title="Edit"><i></i></a>                  <a routerLink="/product/delete/{{product.id}}" title="Delete"><i></i></a>                </td>              </tr>            </tbody>          </table>          <a routerLink="/product/create">Create</a>        </div>      </div>    </div>`})exportclassProductIndex{products?:any[]constructor(publicrouter:Router,privateProductService:ProductService){}ngOnInit(){this.get()}get(){this.ProductService.get().subscribe(data=>{this.products=data},e=>{alert(e.error)})}}
Enter fullscreen modeExit fullscreen mode

TheProductIndex component displays a list of products in a table format. It fetches the list of products fromProductService on initialization and shows each product's ID, name, and price, with action buttons for viewing, editing, and deleting each product. It also includes a button to navigate to the product creation page.

Product.service.ts

import{Injectable}from'@angular/core'import{HttpClient}from'@angular/common/http'import{Observable}from'rxjs'@Injectable({providedIn:'root'})exportclassProductService{constructor(privatehttp:HttpClient){}get(id?:any):Observable<any>{if(id){returnthis.http.get(`/products/${id}`)}else{returnthis.http.get('/products'+location.search)}}create(data?:any):Observable<any>{if(data){returnthis.http.post('/products',data)}else{returnthis.http.get('/products/create')}}edit(id:any,data?:any):Observable<any>{if(data){returnthis.http.put(`/products/${id}`,data)}else{returnthis.http.get(`/products/${id}`)}}delete(id:any,data?:any):Observable<any>{if(data){returnthis.http.delete(`/products/${id}`)}else{returnthis.http.get(`/products/${id}`)}}}
Enter fullscreen modeExit fullscreen mode

TheProductService uses Angular'sHttpClient to perform the relevant HTTP requests for product management. It provides methods to:

  • get(id?): Fetch product details by ID or a list of products if no ID is provided.
  • create(data?): Create a new product ifdata is provided, otherwise fetch the create product page.
  • edit(id, data?): Update a product by ID ifdata is provided, otherwise fetch product details for editing.
  • delete(id, data?): Delete a product by ID ifdata is provided, otherwise fetch product details for confirmation.

styles.css

.container{margin-top:2em;}.btn{margin-right:0.25em;}
Enter fullscreen modeExit fullscreen mode

The CSS adjusts the layout by adding space above the container and spacing out buttons horizontally.

index.html

<!DOCTYPE html><htmllang="en"><head><metacharset="utf-8"><metaname="viewport"content="width=device-width,initial-scale=1"><linkhref="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css"rel="stylesheet"><linkhref="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"rel="stylesheet"></head><body><app-root></app-root></body></html>
Enter fullscreen modeExit fullscreen mode

The HTML serves as the main entry point for an Angular application, including Bootstrap for styling, Font Awesome for icons, and<app-root> as the placeholder for the Angular app.

Setup Laravel API project

composer create-project laravel/laravel api 11.0.3
Enter fullscreen modeExit fullscreen mode

Create a testing database named "example" and execute thedatabase.sql file to import the table and data.

Laravel API Project structure

├─ .env├─ app│  ├─ Http│  │  └─ Controllers│  │     └─ ProductController.php│  └─ Models│     └─ Product.php├─ bootstrap│  └─ app.php└─ routes   └─ api.php
Enter fullscreen modeExit fullscreen mode

*This project structure will display only the files and folders that we plan to create or modify.

Laravel API Project files

.env

DB_CONNECTION=mysqlDB_HOST=localhostDB_PORT=3306DB_DATABASE=exampleDB_USERNAME=rootDB_PASSWORD=
Enter fullscreen modeExit fullscreen mode

This file is used for Laravel configuration, specifically to store the database connection details.

app.php

<?phpuseIlluminate\Foundation\Application;useIlluminate\Foundation\Configuration\Exceptions;useIlluminate\Foundation\Configuration\Middleware;returnApplication::configure(basePath:dirname(__DIR__))->withRouting(web:__DIR__.'/../routes/web.php',api:__DIR__.'/../routes/api.php',commands:__DIR__.'/../routes/console.php',health:'/up',)->withMiddleware(function(Middleware$middleware){//})->withExceptions(function(Exceptions$exceptions){//})->create();
Enter fullscreen modeExit fullscreen mode

This file is a Laravel application configuration file where we have included the API routing setup.

api.php

<?phpuseApp\Http\Controllers\ProductController;Route::resource('/products',ProductController::class);
Enter fullscreen modeExit fullscreen mode

Thisapi.php file sets up resourceful API routes for a Laravel application. It usesRoute::resource to automatically create routes for common CRUD operations (index, show, store, update, destroy) for the/products endpoint, all mapped to the corresponding methods inProductController.

Product.php

<?phpnamespaceApp\Models;useIlluminate\Database\Eloquent\Model;classProductextendsModel{protected$table='Product';protected$primaryKey='id';protected$fillable=['name','price'];public$timestamps=false;}
Enter fullscreen modeExit fullscreen mode

Thisproduct.php file defines aProduct model in a Laravel application. It specifies that the model uses theProduct table, setsid as the primary key, and allows mass assignment for thename andprice fields.

ProductController.php

<?phpnamespaceApp\Http\Controllers;useApp\Models\Product;classProductController{publicfunctionindex(){returnProduct::get();}publicfunctionshow($id){returnProduct::find($id);}publicfunctionstore(){returnProduct::create(['name'=>request()->input('name'),'price'=>request()->input('price')]);}publicfunctionupdate($id){returntap(Product::find($id))->update(['name'=>request()->input('name'),'price'=>request()->input('price')]);}publicfunctiondestroy($id){Product::find($id)->delete();}}
Enter fullscreen modeExit fullscreen mode

TheProductController.php file in a Laravel application defines methods for managingProduct resources:

  • index Retrieves and returns all products.
  • show Fetches and returns a single product by its ID.
  • store Creates a new product using the data from the request.
  • update Updates an existing product with the data from the request.
  • destroy Deletes a product by its ID.

Run projects

Run Angular project

npm start
Enter fullscreen modeExit fullscreen mode

Run Laravel API project

php artisan serve
Enter fullscreen modeExit fullscreen mode

Open the web browser and gotohttp://localhost:4200

You will find this product list page.

list page

Testing

Click the "View" button to see the product details page.

details page

Click the "Edit" button to modify the product and update its details.

edit page

Click the "Submit" button to save the updated product details.

updated data

Click the "Create" button to add a new product and input its details.

create page

Click the "Submit" button to save the new product.

created product

Click the "Delete" button to remove the previously created product.

delete page

Click the "Delete" button to confirm the removal of this product.

deleted product

Conclusion

In conclusion, we have learned how to create a basic Angular project with components, views, and routing, while integrating it with a Laravel API as the backend. By utilizing Eloquent as the ORM for database operations, we've built a responsive front-end that effectively communicates with a robust backend. This approach provides a solid foundation for developing modern, full-stack web applications.

Source code:https://github.com/stackpuz/Example-CRUD-Angular-18-Laravel-11

Create an Angular CRUD App in Minutes:https://stackpuz.com

Top comments(1)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
pbouillon profile image
Pierre Bouillon
Software Engineer | Full Stack Developer | Coffee Lover
  • Location
    France
  • Education
    Engineering degree
  • Pronouns
    He / Him
  • Work
    Full Stack Software Engineer
  • Joined

Thanks for sharing! Note that Angular now focuses on using signals and standalone components, reducing the usage of modules and observables.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Create a CRUD Web App in Minutes.
  • Location
    Chai Nat, Thailand
  • Joined

More fromStackPuz

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp