Current Path : /www/sites/www.coderblog.in/index/
Url:

NameSizeOptions
App_DataDIRnone
backupDIRnone
cgi-binDIRnone
cssDIRnone
imgDIRnone
metaDIRnone
wp-adminDIRnone
wp-contentDIRnone
wp-includesDIRnone
.htaccess3.35 KBDEL
.htaccess.bk1.84 KBDEL
404.html0.13 KBDEL
ads.txt1.07 KBDEL
favicon.ico2.19 KBDEL
index.php0.40 KBDEL
license.txt19.44 KBDEL
php.ini0.58 KBDEL
readme.html7.25 KBDEL
service.php0.00 KBDEL
web.config2.87 KBDEL
wp-activate.php7.21 KBDEL
wp-blog-header.php1.21 KBDEL
wp-comments-post.php2.27 KBDEL
wp-config-sample.php3.26 KBDEL
wp-config.php2.98 KBDEL
wp-cron.php5.49 KBDEL
wp-links-opml.php2.44 KBDEL
wp-load.php3.84 KBDEL
wp-login.php50.21 KBDEL
wp-mail.php8.52 KBDEL
wp-settings.php29.38 KBDEL
wp-signup.php33.71 KBDEL
wp-trackback.php4.98 KBDEL
xmlrpc.php3.13 KBDEL
angular – Coder Blog https://www.coderblog.in Join the coding revolution! Learn, share, and grow together! Sun, 21 Apr 2024 12:20:51 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.1 How to create pre-loading before page load in Angular https://www.coderblog.in/2023/10/how-to-create-pre-loading-before-page-load-in-angular/ https://www.coderblog.in/2023/10/how-to-create-pre-loading-before-page-load-in-angular/#comments Tue, 03 Oct 2023 14:22:58 +0000 https://www.coderblog.in/?p=1103 1. Introduction When you access a website, it needs to load many elements or components and this will

<p>The post How to create pre-loading before page load in Angular first appeared on Coder Blog.</p>

]]>
1. Introduction

When you access a website, it needs to load many elements or components and this will take a few times (based on the network and server workload), for giving users a better experience, we can use a pre-loading to show the page is in loading status.

2. How it’s working

I have written an article for creating a loading in Angular with ngx-loading, the loading for waiting for data return from API, but this time, I will show you a different way and create a global loading with css.

The pre-loading will show before every page is loaded to avoid the case that the page is blank before loaded, so we should put the loading in one global page and don’t need to call it manual every time. So we can use any css to customize the loading style.

Ok, let’s start it!

3. Create the loading with CSS

We can put the loading code in src/index.html, that will be great for putting a global loading, and we just need to use the style sheet to custom the loading, put the below after head

<!-- MyDemo.Client\src\index.html -->

<style type="text/css">
  .global-loader {
    display: flex;
    justify-content: center;
    align-items: center;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    background-color: #fff;
    opacity: 1;
    transition: opacity .5s ease-in-out;
  }

  .global-loader-fade-in {
    opacity: 0;
  }

  .global-loader-hidden {
    display: none;
  }

  .global-loader h1 {
    font-family: "Helvetica Neue", Helvetica, sans-serif;
    font-weight: normal;
    font-size: 24px;
    letter-spacing: .04rem;
    white-space: pre;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    background-image:
      repeating-linear-gradient(
        to right,
        #f44336,
        #9c27b0,
        #3f51b5,
        #03a9f4,
        #009688,
        #8bc34a,
        #ffeb3b,
        #ff9800
      );
    background-size: 750% auto;
    background-position: 0 100%;
    animation: gradient 20s infinite;
    animation-fill-mode: forwards;
    animation-timing-function: linear;
  }

  @keyframes gradient {
    0% {
      background-position: 0 0;
    }

    100% {
      background-position: -750% 0;
    }
  }
  </style>

and put a div for loading text, so the body will be like below

<!-- MyDemo.Client\src\index.html -->

<body>
  <app-root></app-root>
  <div id="globalLoader" class="global-loader"><h1>LOADING</h1></div>
</body>

4. Create a service

The service is very common and useful in Angular, it can help to do many things in multiple components, so we also need to create a service in this case:

//MyDemo.Client\src\app\services\preloader.service.ts

import { Inject, Injectable } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Injectable({
  providedIn: 'root',
})
export class PreloaderService {
  private selector = 'globalLoader';

  constructor(@Inject(DOCUMENT) private document: Document) {}

  private getElement() {
    return this.document.getElementById(this.selector);
  }

  hide() {
    const el = this.getElement();
    if (el) {
      el.addEventListener('transitionend', () => {
        el.className = 'global-loader-hidden';
      });

      if (!el.classList.contains('global-loader-hidden')) {
        el.className += ' global-loader-fade-in';
      }
    }
  }
}

As you can see, we use the service to hide the loading, so it will show by default when the page is loading, and it will be hidden after the page is loaded

5. Use the service

We can hide the loading in app.component.ts :

import { Component, OnInit, AfterViewInit } from '@angular/core';
import { PreloaderService } from './services/preloader.service';


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, AfterViewInit {
  constructor(private preloader: PreloaderService) {}

  title = 'MyDemo.Client';

  ngOnInit() {
  }

  ngAfterViewInit() {
    this.preloader.hide();
  }
}

The code is very simple, just handling the hide action in ngAfterViewInit event will be ok~

6. Conclusion

This is the simple way to handle page pre-loading issues, and don’t need to use other libs, you can also create any other loading styles with CSS, but this is unable to replace the ngx-loading,, if you want, you can try to use ngx-loading to replace the CSS loading 🙂

Loading

<p>The post How to create pre-loading before page load in Angular first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2023/10/how-to-create-pre-loading-before-page-load-in-angular/feed/ 14
Creating the loading in Angular https://www.coderblog.in/2023/09/creating-the-loading-in-angular/ https://www.coderblog.in/2023/09/creating-the-loading-in-angular/#comments Mon, 11 Sep 2023 01:26:23 +0000 https://www.coderblog.in/?p=1085 1. Introduction Loading is essential in a website, it can let the user know the flow is processing,

<p>The post Creating the loading in Angular first appeared on Coder Blog.</p>

]]>
1. Introduction

Loading is essential in a website, it can let the user know the flow is processing, otherwise user will not know what’s happening. So, I will introduce an excellent loading package to help you solve the loading issue!

2. ngx-loading

ngx-loading is a very nice and customizable loading spinner for Angular, there are flexible config options that let you setup different loading styles. You can install ngx-loading with the below command

npm install --save ngx-loading

And you can find the base usage in github

3. Use in the common layout

With normal usage, you need to add the below code in each html page, so in this article, I will show you how to use the ngx-loading in a common layout.

<ngx-loading
    [show]="loading"
    [config]="{ backdropBorderRadius: '3px' }"
    [template]="customLoadingTemplate"
></ngx-loading>

3.1. Create the service

The concept is we will put the ngx-loading in the common (or parent) page’s HTML layout, so we can’t control it in the child page directly, the solution is we can use a Subject.

The Subject is a special type of Observable that allows values to be multicasted to many Observers. Subjects are like EventEmitters.

So we can create a service to handle the Subject. Suppose we just need to control start and stop to show the loading in needs, so create these two methods in the service, and don’t forget to release the object, the complete code as below:

import { Injectable, OnDestroy } from "@angular/core";
import { Subject } from "rxjs";


@Injectable({
  providedIn: 'root',
})
export class LoadingService implements  OnDestroy {
  // Observable string sources
  private emitChangeSource = new Subject<boolean>();
  // Observable string streams
  changeEmitted$ = this.emitChangeSource.asObservable();

  // Start loading
  start() {
    this.emitChangeSource.next(true);
  }

  // Stop loading
  stop() {
    this.emitChangeSource.next(false);
  }

  ngOnDestroy() {
    // complete and release the subject
    this.emitChangeSource.complete();
  }
}

3.2. Add the ngx-loading to the layout

1) Import the NgxLoadingModule in your root app module

import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { CoreModule } from "./core/core.module";
import { NgxLoadingModule } from "ngx-loading";

@NgModule({
  //...
  imports: [
    //...
    NgxLoadingModule.forRoot({}),
  ],
  //...
})
export class AppModule {}

2) Create a loading flag and config in the main layout backend(.ts) file

const PrimaryRed = '#dd0031';
const SecondaryBlue = '#1976d2';

//...

//set the loading flag
loading = false;
//set the loading config
//reference:  https://github.com/Zak-C/ngx-loading#config-options
config = {
  animationType: ngxLoadingAnimationTypes.threeBounce,
  primaryColour: PrimaryRed,
  secondaryColour: SecondaryBlue,
  tertiaryColour: PrimaryRed,
  backdropBorderRadius: '3px',
};

3) Subscribe and update the loading status in the main layout

// Update loading status
this.loadingService.changeEmitted$.subscribe(isLoading => {
  //console.log(isLoading);
  this.loading = isLoading;
});

4) Add the loading HTML code in the main layout frontend(.html) file

<ngx-loading #ngxLoading [show]="loading" [config]="config" [template]="loadingTemplate"></ngx-loading>
  <ng-template #loadingTemplate>
    <div class="loading-class">
      <h4>Please wait ...</h4>
    </div>
</ng-template>

Ok, now, we just need to control the loading flag in other child pages with LoadingService

4. Usage in child pages

We still using this code for the demo. Apply the loading when doing login, so we set the private loadingService: LoadingService in the login component’s constructor, and call this.loadingService.start(); after submitting the login form, and then call this.loadingService.stop(); when success or failed login:

  constructor(//...
    private loadingService: LoadingService) {
  }

  //login
login() {

 //start the loading
 this.loadingService.start();

 this.auth
   .login(this.username.value, this.password.value)
   .pipe(filter(authenticated => authenticated))
   .subscribe(
     () => {
       console.log('after logined and redirect');

       //stop the loading
       this.loadingService.stop();

       this.router.navigateByUrl('/user-management');
     },
     (errorRes: HttpErrorResponse) => {

       //stop the loading
       this.loadingService.stop();

       if(errorRes.status == 401){
        //...
       }
       console.log('Error', errorRes);
     }
   );
}

After clicking the login button, it will show the loading as below

Figure 1

5. Conclusion

If we want to control the parent component in the child, we need to subscribe to the change status and update it in the child, so we can use the Subject and create a service for that! This is the same concept as this article to use the SweetAlert2 in angular.

Loading

<p>The post Creating the loading in Angular first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2023/09/creating-the-loading-in-angular/feed/ 4
Use the SweetAlert2 in Angular Common Layout https://www.coderblog.in/2023/08/use-the-sweetalert2-in-angular-common-layout/ https://www.coderblog.in/2023/08/use-the-sweetalert2-in-angular-common-layout/#comments Wed, 30 Aug 2023 07:31:24 +0000 https://www.coderblog.in/?p=1063 1. Introduction SweetAlert2 is a beautiful, responsive, customizable, accessible replacement for javascript’s popup boxes, and there is also

<p>The post Use the SweetAlert2 in Angular Common Layout first appeared on Coder Blog.</p>

]]>
1. Introduction

SweetAlert2 is a beautiful, responsive, customizable, accessible replacement for javascript’s popup boxes, and there is also a version for Angular, you can find the detail usage in official website. But, the use cases in the official doc are just for use on one page, which means you need to add the same HTML codes on every page you want to use, what if you are using the common layout? The common layout means there is a main layout including Header,Center Body, and Footer, just like this article which I introduced.

In the common layout structure, you don’t need to add the SweetAlert2 HTML code on every page, just adding it to the main layout will be ok! So in this article, I will show you how to do it.

2. Install SweetAlert2 for Angular

Cut of the day I wrote this article, the latest version is 12.2.0, but there is an error for this version, so I suggest installing version 12.1.0

npm install sweetalert2 @sweetalert2/ngx-sweetalert2@12.1.0

import the SweetAlert2 in app.module

import { SweetAlert2Module } from '@sweetalert2/ngx-sweetalert2';

...

@NgModule({
    ...
    imports:[
        ...
        SweetAlert2Module.forRoot(),
        ...
    ]
    ...
})

Every Subject is an Observable and an Observer. You can subscribe to a Subject, and you can call next to feed values as well as error and complete.

3. Create the SweetAlert2 service

If we want to put the SweetAlert2 in a common main layout, then we need to use the observer pattern, we can use a service to subscribe to an observer and then update or call the SweetAlter2 methods.

So, we create the Subject to do that, we need to subscribe 3 items:

1) SweetAlertOptions : All of the sweet alert options, because we need to dynamic to change these options in difference page, so need to subscribe to it
2) Whether Close the SweetAlter2 : This is a boolean object for detecting whether close the SweetAlter2
3) Confirm Item : This is a custom interface for handling the confirm dialog action

The code snippets as below

private swalSource = new Subject<SweetAlertOptions>();
swalEmitted$ = this.swalSource.asObservable();

private swalCloseSource = new Subject<boolean>();
swalCloseEmitted$ = this.swalCloseSource.asObservable();

private swalConfirmSource = new Subject<SwalConfirmItem>();
swalConfirmEmitted$ = this.swalConfirmSource.asObservable();


/**
 * Handle confirm action and data
 */
export interface SwalConfirmItem {
  //the confirm handler function
  fnConfirm: any;
  //The data needs to be passed to the confirm function
  confirmData: any;
  //the current context of the component
  context: any;
}

we can use the next method to execute the subject, so create the show and close method

 // Show swal with options
 // icon: SweetAlertIcon = 'success' | 'error' | 'warning' | 'info' | 'question'
 show(options: SweetAlertOptions) {
   this.swalSource.next(options);
 }

 // Close the swal
 close() {
   this.swalCloseSource.next(true);
 }

 // Set the confirm event
 setConfirm(confirmItem: SwalConfirmItem) {
   this.swalConfirmSource.next(confirmItem);
 }

and we can also create a method for displaying the HttpErrorResponse object

// Handle the HttpErrorResponse and show the error box
showErrors(error: any, options: SweetAlertOptions) {
  console.log('%c [ error ]-37', 'font-size:13px; background:pink; color:#bf2c9f;', error);
  if (error.error && error.error.errors) {
    var errors = '';
    for (var key in error.error.errors) {
      errors += error.error.errors[key] + '<br>';
    }
    options.html = error.error.title + '<br>' + errors;
  } else {
    options.html = error.error;
  }
  options.icon = 'error';
  //show the dialog
  this.swalSource.next(options);
}

maybe the error object will be different from yours (based on the API return values), you can print the error object in the console and find the correct way for looping it.

and don’t forget to destroy the Subject object at the end to void the memory lake

ngOnDestroy() {
  //Complete and release the subject
  this.swalSource.complete();
  this.swalCloseSource.complete();
  this.swalConfirmSource.complete();
}

So, grouping all together will be a SwalService

//MyDemo.Client\src\app\services\swal.service.ts

import { Injectable, OnDestroy } from "@angular/core";
import { Subject } from "rxjs";
import { SweetAlertOptions } from "sweetalert2";

//reference: https://github.com/sweetalert2/ngx-sweetalert2

@Injectable({
  providedIn: 'root',
})
export class SwalService implements OnDestroy {

  private swalSource = new Subject<SweetAlertOptions>();
  swalEmitted$ = this.swalSource.asObservable();

  private swalCloseSource = new Subject<boolean>();
  swalCloseEmitted$ = this.swalCloseSource.asObservable();

  private swalConfirmSource = new Subject<SwalConfirmItem>();
  swalConfirmEmitted$ = this.swalConfirmSource.asObservable();


  // Show swal with options
  // icon: SweetAlertIcon = 'success' | 'error' | 'warning' | 'info' | 'question'
  show(options: SweetAlertOptions) {
    this.swalSource.next(options);
  }

  // Close the swal
  close() {
    this.swalCloseSource.next(true);
  }

  // Handle the HttpErrorResponse and show the error box
  showErrors(error: any, options: SweetAlertOptions) {
    console.log('%c [ error ]-37', 'font-size:13px; background:pink; color:#bf2c9f;', error);

    if (error.error && error.error.errors) {
      var errors = '';
      for (var key in error.error.errors) {
        errors += error.error.errors[key] + '<br>';
      }
      options.html = error.error.title + '<br>' + errors;
    } else {
      options.html = error.error;
    }

    options.icon = 'error';
    this.swalSource.next(options);
  }

  // Set the confirm event
  setConfirm(confirmItem: SwalConfirmItem) {
    this.swalConfirmSource.next(confirmItem);
  }

  ngOnDestroy() {
    // complete and release the subject
    this.swalSource.complete();
    this.swalCloseSource.complete();
    this.swalConfirmSource.complete();
  }
}

/**
 * Handle confirm action and data
 */
export interface SwalConfirmItem {
  //the confirm handler function
  fnConfirm: any;
  //the data need to be pass to the confirm function
  confirmData: any;
  //the current context of the component
  context: any;
}

4. Create the Main layout

Create the main layout component with the below command

ng g c MainLayout --skip-tests

For demonstrate, we just use the simple layout below, add the SweetAlert2 in the main layout, we need to dynamic to control the visible status and the confirm action

<!-- MyDemo.Client\src\app\main-layout\main-layout.component.html -->

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Demo Testing</title>

</head>

<body>
<div>Header content</div>

<div id="wrapper">
  <router-outlet></router-outlet>
</div>

<div>Footer content</div>

<swal #swalBox  [swalVisible]="isSwalVisible" (confirm)="handleConfirm($event, swalConfirmData, swalComponentContext)"></swal>

</body>
</html>

And define the variables in the backend ts file, we use the @ViewChild to show the swal object in frontend

isSwalVisible = false;
swalConfirmData : any;
swalComponentContext: any;

@ViewChild(SwalComponent) swalBox!: SwalComponent;
swalOptions: SweetAlertOptions = {};

It need to subscribe and fire the swal from the child component, so use the swal service in the constructor

constructor(private swalService: SwalService){
  //fire the swal from child component
  this.swalService.swalEmitted$.subscribe(options => {
    if(!this.swalBox){
      //just wait for the time to load the object if can't find it
      setTimeout(()=>{
        if(this.swalBox){
          this.isSwalVisible = true;
          this.swalBox.update(options);
          this.swalBox.fire();
        }
      }, 500);
    }
    else {
      this.isSwalVisible = true;
      this.swalBox.update(options);
      this.swalBox.fire();
    }
  });

  //set the confirm function and execute the login in the child component
  this.swalService.swalConfirmEmitted$.subscribe(confirmItem => {
    this.handleConfirm = confirmItem.fnConfirm == null ? this.resetHandleConfirm : confirmItem.fnConfirm;
    this.swalConfirmData = confirmItem.confirmData;
    this.swalComponentContext = confirmItem.context;
  });

  //handle close the swal event
  this.swalService.swalCloseEmitted$.subscribe(item => {
    this.swalBox.close();
  });
}

Here we need to create the handle confirm method and it will be override by this.swalService.swalConfirmEmitted$, that’s mean will be process the logic in each child component, so just keep empty in here

handleConfirm(item: string, data: any, context: any): void {
   //this will be overwrite by this.swalService.swalConfirmEmitted$
}

Another handle method is resetHandleConfirm, this is only an empty method, but why do we need to use this?

When you call the confirm dialog once time, it will keep the handle method for next time to use, this will cause the handler will be called again and again. To avoid this case, we need to reset the handler method, just setting an empty method will be ok!

//just for reset(remove) the existing handle confirm function
resetHandleConfirm(item: string, data: any, context: any): void {
  //this will be overwrite by this.swalService.swalConfirmEmitted$
}

If the confirm function is null, then use this empty function

this.handleConfirm = confirmItem.fnConfirm == null ? this.resetHandleConfirm : confirmItem.fnConfirm;

In the end, update the app-routing.module.ts, and set all the components under the main layout, so that can be used the main layout structure

const routes: Routes = [
  {
    path: '',
    component: MainLayoutComponent,
    children: [
      { path: 'user-management', component: UserManagementComponent, canActivate: [AuthGuard] },
      { path: 'form-layout', component: FormLayoutTestingComponent, canActivate: [AuthGuard] },
      { path: 'login', component: LoginComponent }
    ]
  }
];

5. Usage

Now, we can try to use the SweetAlter2 in the child component.

5.1. Show the message box

We will show the error if login failed on login page:

1) First, inject the SwalService in the constructor

constructor(private fb: FormBuilder, 
    private router: Router, 
    private auth: AuthService,
    private swalService: SwalService) {
  }

2) Define the SweetAlertOptions and set the default icon to info

swalOptions: SweetAlertOptions = { icon : 'info' };

3) Show the error after login failed

login() {
    console.log('user name:' + this.username.value);
    this.auth
      .login(this.username.value, this.password.value)
      .pipe(filter(authenticated => authenticated))
      .subscribe(
        () => {
          console.log('after logined and redirect');
          this.router.navigateByUrl('/user-management');
        },
        (errorRes: HttpErrorResponse) => {
          if(errorRes.status == 401){

            // set the swal icon to 'error'
            this.swalOptions.icon = 'error';
            // set the message need to be show 
            this.swalOptions.html =  'User name or password is not valid!';
            // show the swal box
            this.swalService.show(this.swalOptions);
          }

          console.log('Error', errorRes);
        }
      );
  }

The result as below:

Figure 1

5.2. Show the confirm dialog

We will show the confirm dialog when try to delete a user.

To support deleting users, we need to add the button for each row in MtxGridColumn. You can find the details for how to use it in this article.

{
  header: 'Operation', field: 'operation' ,
  minWidth: 140,
  pinned: 'left',
  type: 'button',
  buttons: [
    {
      color: 'warn',
      icon: 'delete',
      text: 'Delete',
      tooltip: 'Delete',
      click: record => this.delete(record),
    },
  ],
},

Then you will find the delete icon in each row

Figure 2

If you can’t see the icon, just take a look at whether add it below in your index.html file

<!-- MyDemo.Client\src\index.html -->
<link href="assets/fonts/Material_Icons.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

Ok, we need to implement the this.delete() function, this is the delete confirm function, so we can use the SwalService to show the confirm dialog

public delete(value: any) {
  this.swalOptions.icon = 'question';
  this.swalOptions.title = 'Delete User';
  this.swalOptions.html = `Do you confirm to delete the user ${value.name}?`;
  this.swalOptions.showConfirmButton = true;
  this.swalOptions.showCancelButton = true;

  this.confirmItem.confirmData = value; //pass the data to delete confirm function
  this.confirmItem.fnConfirm = this.deleteConfirm;
  this.swalService.setConfirm(this.confirmItem);
  this.swalService.show(this.swalOptions);
}

As you can see, the this.swalOptions is the SweetAlert2 native setting value, we need to pass the current value (should be user ID in this case) to confirm data, and pass the delete confirm function to override the main layout function, the delete confirm function need to handle the confirm event, that’s mean if user confirmed to delete, will be execute the delete function and call the API to delete user

public deleteConfirm(isConfirm: string, data: any, context: any): void {
  //console.log('delete item', data);
  // call the service to delete user
  context.userManagementService.delete(data.id).subscribe((result: { success: any; message: string | HTMLElement | JQuery | undefined; }) => {
    this.swalOptions.showCancelButton = false; //just need to show the OK button
    context.confirmItem.fnConfirm = null;//reset the confirm function to avoid call this function again and again.
    this.swalService.setConfirm(context.confirmItem);
    if(result.success){
      this.swalOptions.icon = 'success'; //set the icon
      this.swalOptions.html = `The user ${data.name} has been deleted!`;
    } else {
      this.swalOptions.icon = 'error';
      this.swalOptions.html = result.message;
    }
    this.swalService.show(this.swalOptions);
    context.loadData();
  }, (error: { error: any; }) => {
    console.error(error);
    this.swalService.showErrors(error.error, this.swalOptions);
  });
}

Let’s try and see the result:

Figure 3

Figure 4

6. Conclusion

SweetAlert2 is a very nice popup message box, and I also use it in other projects, but it will be a little complex in Angular with main/children component structure, that’s why I wrote this article, and hope this can help more people!

In the end, I will provide the full source code below:

https://github.com/coderblog-winson/MyDemo

Loading

<p>The post Use the SweetAlert2 in Angular Common Layout first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2023/08/use-the-sweetalert2-in-angular-common-layout/feed/ 9
How to Implement JWT in Core API and Angular — Part 2 https://www.coderblog.in/2023/08/how-to-implement-jwt-in-core-api-and-angular-part-2/ https://www.coderblog.in/2023/08/how-to-implement-jwt-in-core-api-and-angular-part-2/#comments Fri, 25 Aug 2023 01:02:40 +0000 https://www.coderblog.in/?p=1048 This is post 2 of 2 in the series “How to Implement JWT” In this series, I will

<p>The post How to Implement JWT in Core API and Angular — Part 2 first appeared on Coder Blog.</p>

]]>
  1. How to Implement JWT in Core API and Angular – Part 1
  2. How to Implement JWT in Core API and Angular — Part 2

1. Introduction

We have introduced how to generate the JWT on the server side in previous article, so we will continue to introduce how to handle the JWT in client-side with Angular in this article.

For demonstration, we need to create a simple login page to call the login API, and create another simple page for checking the login token whether is valid, OK, let’s do it!

2. Create the Models

I will base it on the previous demo project for describing. To communicate with the API, we need to create the corresponding models to map the result, so we create a login-request to pass the login data

//MyDemo.Client\src\app\models\login-request.ts

export interface LoginRequest {
  username: string;
  password: string;
}

and create a token model

//MyDemo.Client\src\app\models\token.ts

export interface Token {
  [prop: string]: any;

  //the Jwt token return from API after login successfully
  access_token: string;
  //the current user id
  user_id?: string;
  //should be just handle the 'Bearer' type in this sample
  token_type?: string;
  //How long will be the token expired(e.g. after 30 mins). This is a timestamp format
  expires_in?: number;
  //the actually expire time, so should be the expires_in + current time, e.g. 
  //if expires_in = 30 mins, then exp would be current time + 30 mins
  exp?: number;
}

3. Create the Services

There are 3 services we need to handle:

3.1. Handle Token

First, we need to create a jwt-token to handle the token logic, put these logics into a core folder

//MyDemo.Client\src\app\core\jwt-token.ts

import { Token } from "../models/token";
import { capitalize, currentTimestamp } from "./util";

export class JwtToken {
  constructor(protected attributes: Token) {}

  get access_token(): string {
    return this.attributes.access_token;
  }

  get user_id(): string {
    return this.attributes.user_id ?? '';
  }

  get token_type(): string {
    return this.attributes.token_type ?? 'bearer';
  }

  get exp(): number | void {
    return this.attributes.exp;
  }

  valid(): boolean {
    return this.hasAccessToken() && !this.isExpired();
  }

  getBearerToken(): string {
    return this.access_token
      ? [capitalize(this.token_type), this.access_token].join(' ').trim()
      : '';
  }

  private hasAccessToken(): boolean {
    return !!this.access_token;
  }

  /**
  Check the expired time
  Unit: seconds
  */
  private isExpired(): boolean {
    return this.exp !== undefined && this.exp - currentTimestamp() <= 0;
  }

}

create the util for the common helper methods

//MyDemo.Client\src\app\core\util.ts

/**
 * Capitalize first letter
 * @param text the text wants to be capitalized
 * @returns
 */
export function capitalize(text: string): string {
  return text.substring(0, 1).toUpperCase() + text.substring(1, text.length).toLowerCase();
}

/**
 * Get the current timestamp
 * @returns
 */
export function currentTimestamp(): number {
  return Math.ceil(new Date().getTime() / 1000);
}

/**
 * Filter the Non null object to make sure the object is valid
 * @param obj filter object
 * @returns
 */
export function filterObject<T extends Record<string, unknown>>(obj: T) {
  return Object.fromEntries(
    Object.entries(obj).filter(([, value]) => value !== undefined && value !== null)
  );
}

because we need to save the token into local storage, so create a simple local storage service

//MyDemo.Client\src\app\services\local-storage.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class LocalStorageService {
  get(key: string) {
    return JSON.parse(localStorage.getItem(key) || '{}') || {};
  }

  set(key: string, value: any): boolean {
    localStorage.setItem(key, JSON.stringify(value));

    return true;
  }

  has(key: string): boolean {
    return !!localStorage.getItem(key);
  }

  remove(key: string) {
    localStorage.removeItem(key);
  }

  clear() {
    localStorage.clear();
  }
}

in the end, create a token service to put these together

//MyDemo.Client\src\app\services\token.service.ts

import { Injectable, OnDestroy } from '@angular/core';
import { Token } from '../models/token';
import { LocalStorageService } from './local-storage.service';
import { JwtToken } from '../core/jwt-token';
import { currentTimestamp, filterObject } from '../core/util';

@Injectable({
  providedIn: 'root',
})
export class TokenService implements OnDestroy {
  private key = 'MyDemo-token';

  private _token?: JwtToken;

  constructor(private store: LocalStorageService) {}

  private get token(): JwtToken | undefined {
    if (!this._token) {
      this._token = new JwtToken(this.store.get(this.key));
    }

    return this._token;
  }

  set(token?: Token): TokenService {
    this.save(token);

    return this;
  }

  clear(): void {
    this.save();
  }

  valid(): boolean {
    return this.token?.valid() ?? false;
  }

  getUserid(): string {
    return this.token?.user_id ?? '';
  }

  getBearerToken(): string {
    return this.token?.getBearerToken() ?? '';
  }

  ngOnDestroy(): void {
  }

  /**
   * Save the token to local storage
   * @param token token model
   */
  private save(token?: Token): void {

    this._token = undefined;

    if (!token) {
      this.store.remove(this.key);
    } else {
      const value = Object.assign({ access_token: '', token_type: 'Bearer' }, token, {
        exp: token.expires_in ? currentTimestamp() + token.expires_in : null,
      });
      this.store.set(this.key, filterObject(value));
    }
  }
}

3.2. Authentication Service

We will call the API to login the system and get the Jwt token, so we need an auth service to handle the login and logout logic

import { Injectable } from '@angular/core';
import { map, tap } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import { config } from 'src/assets/config';
import { Token } from '../models/token';
import { TokenService } from './token.service';
import { Router } from '@angular/router';

@Injectable({
  providedIn: 'root',
})
export class AuthService {

  constructor(
    private tokenService: TokenService, private router: Router,
    protected http: HttpClient) {}

    /**
     * Call the API to login
     * @param username user name
     * @param password password
     * @returns Jwt token if login successfully
     */
    login(username: string, password: string) {

      var url = config.apiUrl + "/auth/login";
      //call the API to get token after login successfully
      return this.http.post<Token>(url, { username, password }).pipe(
        tap(token => {
          console.log('auth service logined ', token);
          //save the token into local storage
          this.tokenService.set(token);
        }),
        map(() => {
          console.log('auth service logined and map ', this.check());
          //check the token whether is valid
          return this.check();
        })
      );
    }

    /**
     * Clear the token after logout
     */
    logout(){
      this.tokenService.clear();
      this.router.navigateByUrl('/login');
    }

    check() {
      return this.tokenService.valid();
    }
}

4. Create the Login page

After creating the essential models and services, we can create the login page now. Run the below command to create a login page

ng g c Login --skip-tests

Cause we are just for demonstration, so only need to create a simple login layout :

<!-- MyDemo.Client\src\app\login\login.component.html -->

<p>Login</p>
<div class="row">
  <form class="form-field-full" [formGroup]="loginForm">
  <div class="col-sm-12">
      <mat-form-field class="col-sm-3">
        <mat-label>User Name: </mat-label>
        <input matInput formControlName="username" placeholder="User Name">
      </mat-form-field>
  </div>
  <div  class="col-sm-12">
      <mat-form-field class="col-sm-3">
        <mat-label>Password: </mat-label>
        <input matInput formControlName="password" type="password" placeholder="Password">
      </mat-form-field>

  </div>
  <div  class="col-sm-12">
    <button class="m-r-8 bg-green-700 text-light" mat-raised-button  (click)="login()">Login</button>
  </div>
  <div class="col-sm-12">
    <span style="color: red;" *ngIf="errorMsg != ''">{{ errorMsg }}</span>
  </div>
</form>
</div>

we can use the FormBuilder to get the input values and pass them to the login function

//MyDemo.Client\src\app\login\login.component.ts

import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { FormBuilder } from '@angular/forms';
import { filter } from 'rxjs/operators';
import { HttpErrorResponse } from '@angular/common/http';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent {

  constructor(private fb: FormBuilder, 
          private router: Router, 
          private auth: AuthService) {
  }

  public errorMsg: string = '';

  //init the loginForm 
  loginForm = this.fb.nonNullable.group({
    username: '',
    password: '',
  });

  get username() {
    return this.loginForm.get('username')!;
  }

  get password() {
    return this.loginForm.get('password')!;
  }

  login() {
    this.auth
      .login(this.username.value, this.password.value)
      .pipe(filter(authenticated => authenticated))
      .subscribe(
        () => {
          //redirect to user management page if login successfully
          this.router.navigateByUrl('/user-management');
        },
        (errorRes: HttpErrorResponse) => {
          //otherwise then update the error message in the page
          if(errorRes.status == 401){
            this.errorMsg = 'User name or password is not valid!';
          }
            console.log('Error', errorRes);
        }
      );
  }
}

Ok, we can take a look at the result :

1) Input the user name and password and click login

2) It will return the token (can find it in local storage ) and redirect to the user management page

Seems great, right? 🙂

But please hold on, we still have some problems. You will find that if you access the user management page directly and it also can be successful, that means the page didn’t check the login token, that does not make sense.

5. Add the guard for the user pages

We can solve the above issue with guard in Angular. The guard in Angular refers to route guards, which are interfaces that allow you to control navigation and access to routes in your Angular application. route guards allow you to check if a user can activate or deactivate a route, by implementing CanActivate or CanDeactivate interfaces. You can find more details here.

Create the route guards as below:

//MyDemo.Client\src\app\core\auth.guard.ts

import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  CanActivate,
  CanActivateChild,
  RoutesRecognized,
  Router,
  RouterStateSnapshot,
  UrlTree,
} from '@angular/router';
import { filter, pairwise } from 'rxjs/operators';
import { AuthService } from '../services/auth.service';

@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivate, CanActivateChild {

  previousUrl!: string;
  currentUrl!: string;

  constructor(private auth: AuthService, private router: Router) {
    this.router.events
      .pipe(filter((evt: any) => evt instanceof RoutesRecognized), pairwise())
      .subscribe((events: RoutesRecognized[]) => {
        this.previousUrl = events[0].urlAfterRedirects;
        this.currentUrl = events[1].urlAfterRedirects;
      });
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    return this.authenticate();
  }

  canActivateChild(
    childRoute: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean | UrlTree {
    return this.authenticate();
  }

  private authenticate(): boolean | UrlTree {
    //check whether is login successfully
    if (this.auth.check()) {
      return true;
    }
    else{
      this.router.navigateByUrl('/login');
    }
    return false;
  }
}

and use it in app routing, add the canActivate in the routes

//MyDemo.Client\src\app\app-routing.module.ts

const routes: Routes = [
  { path: 'user-management', component: UserManagementComponent, canActivate: [AuthGuard] },
  { path: 'login', component: LoginComponent },
];

after that, logout of the page and try to access the user management page directly, you will find that will be auto redirected to the login page!

6. Create the Interceptor

We are almost done, but still, there is an issue that needs to be solved! Even if we can login successfully and redirect to the user management page, we still can’t get the user data, because there is an authorized checking with the user controller API, so we need to pass the token to the API when we get the user data.

We can append the HTTP header with Authorization when calling the get user API /api/users, but we also need to do that for every API, so this is not a good approach!

The best way that we can use the interceptor.

Interceptors in Angular are services that allow you to intercept and transform HTTP requests and responses between your application and the server. Request interceptors can modify headers, add authentication tokens, log requests, etc.

Create the token-interceptor as below

//MyDemo.Client\src\app\core\token-interceptor.ts

import { Injectable } from '@angular/core';
import {
  HttpErrorResponse,
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
} from '@angular/common/http';
import { Router } from '@angular/router';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { TokenService } from '../services/token.service';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  constructor(
    private tokenService: TokenService,
    private router: Router
  ) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    const handler = () => {
      //check the url if login then just redirect to user management page after login     
      if (this.router.url.includes('/login')) {
        this.router.navigateByUrl('/user-management');
      }
    };

    if (this.tokenService.valid()) {
      //if the token is valid, then append to the http header for each request
      return next
        .handle(
          request.clone({            
            headers: request.headers.append('Authorization', this.tokenService.getBearerToken()),
            withCredentials: true,
          })
        )
        .pipe(
          catchError((error: HttpErrorResponse) => {
            //error handler
            if (error.status === 401) {
              this.tokenService.clear();
            }
            return throwError(error);
          }),
          tap(() =>{
            handler();})
        );
    }

    return next.handle(request).pipe(tap(() =>{
        handler();
      }));
  }
}

add a provider in app.module

@NgModule({
  ...
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true },
  ],
  ...
})

after that, the TokenInterceptor will auto append the token in http header for each request.

login to the page again, you will see all of the user data as well!

7. Conclusion

Jwt is a better way to handle the API authorization, after this article, we learned how to handle the Jwt token in Angular, the flow should be as below:

1) Call API login function and get the token after successfully
2) Save the token in local storage
3) Add the checking for each user’s pages
4) Pass the token to each API request to get data

In the end, don’t forget there are two main points that create the router guard for checking login token and an interceptor for sending token to API.

Loading

<p>The post How to Implement JWT in Core API and Angular — Part 2 first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2023/08/how-to-implement-jwt-in-core-api-and-angular-part-2/feed/ 7
Using formly for creating dynamic form in Angular https://www.coderblog.in/2023/08/using-formly-for-creating-dynamic-form-in-angular/ https://www.coderblog.in/2023/08/using-formly-for-creating-dynamic-form-in-angular/#comments Thu, 17 Aug 2023 00:36:39 +0000 https://www.coderblog.in/?p=986 1. Introduction The web form is necessary for a website. And there are 2 types of forms in

<p>The post Using formly for creating dynamic form in Angular first appeared on Coder Blog.</p>

]]>
1. Introduction

The web form is necessary for a website. And there are 2 types of forms in Angular


Reactive forms : Provide direct, explicit access to the underlying form’s object model. Compared to template-driven forms, they are more robust: they’re more scalable, reusable, and testable. If forms are a key part of your application, or you’re already using reactive patterns for building your application, use reactive forms.

Template-driven forms : Rely on directives in the template to create and manipulate the underlying object model. They are useful for adding a simple form to an app, such as an email list signup form. They’re straightforward to add to an app, but they don’t scale as well as reactive forms. If you have very basic form requirements and logic that can be managed solely in the template, template-driven forms could be a good fit.

And today, I will show you a 3 party form existence that can help you to create a nice dynamic form, it’s Formly and built on top of Reactive Forms.

Formly is a powerful extension for generating forms, you can dynamic to pass the data for creating a web form and don’t need to write the HTML in frontend. For example, you can get the JSON from API and create the form, all of the settings can be done in a JSON format! And there is a bunch of themes you can use!

Ok, let’s start it!

2. Base Form Layout

2.1. Install Formly

There are 7 themes that support for Formly,

bootstrap
material
ng-zorro-antd
ionic
primeng
kendo
nativescript

you can install with one of the themes as below command, and I just want to use material 🙂

npm install @angular/forms @ngx-formly/core @ngx-formly/material --save

2.2. Create the base form layout component

Create the form layout component without a test file

ng g c FormLayoutTesting --skip-tests

add the below module in app.moudle.ts

@NgModule({

    imports: [
        ReactiveFormsModule,
        FormlyModule,
        FormlyMaterialModule
    ]
})

set the fields with FormlyFieldConfig, this is a JSON format to config the fields data, and use the model for setting the default value

//form-layout-testing.component.ts

  form = new FormGroup({});
  model = { email: 'email@gmail.com' };

  public fields: FormlyFieldConfig[] = [
    {
      key: 'name',
      type: 'input',
      props: {
        label: 'User Name',
        placeholder: 'Enter user name',
        required: true,
      }
    },
    {
      key: 'email',
      type: 'input',
      props: {
        label: 'Email address',
        placeholder: 'Enter email',
        required: true,
      }
    }
  ];

put the formly-form inside the form tag in HTML code

<!-- form-layout-testing.component.html -->
<form [formGroup]="form">
  <formly-form [form]="form" [fields]="fields" [model]="model"></formly-form>
  <div mat-dialog-actions>
  <button mat-raised-button color="primary">Submit</button>
</div>
</form>

then you will see the below screen after running the project

Figure 1

3. Advance usages

The formly is very powerful, I will show you how it’s working 🙂

3.1. Validations

We can easy to check the email format with regular express in the form with pattern , and set the error message

{
  key: 'email',
  type: 'input',
  props: {
    label: 'Email address',
    placeholder: 'Enter email',
    pattern: /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/,
    required: true,
  },
  validation: {
    messages: {
      pattern: (error: any, field: FormlyFieldConfig) => `"${field?.formControl?.value}" is not a valid email Address`,
    },
  },
}

you can also create a custom validation function. For example, I want to check and make sure the website name must be coder blog, then create a custom validator function up on @Comonent

export function webSiteNameValidator(control: AbstractControl): ValidationErrors {
  if (control.value) {
    var name = control.value; //get the input value for checking
    console.log(control.value);
    if (name != null && name != "coder blog") {
      return { error: { message: 'The website name must be "coder blog"!' } };
    }
  }
  return {};
}

@Component({
  selector: 'app-form-layout-testing',
  templateUrl: './form-layout-testing.component.html',
  styleUrls: ['./form-layout-testing.component.scss']
})

set the validation method

{
  key: 'website_name',
  type: 'input',
  props: {
    label: 'Website Name',
    placeholder: 'Enter website name',
    required: true,
  },
  validators: {
    validation: [webSiteNameValidator]
  }
},

and then must be input code blog in the frontend, otherwise it would not valid :smirk:

Figure 2

4. Preset Fields

This is also a great function that lets you preset the common fields so that you don’t need to input the same settings in each component. For example, the Is Active should be applied for each table, so we can set it to a preset field:

a) Importing the FormlyPresetModule into app.module.ts

import { FormlyPresetModule } from '@ngx-formly/core/preset';

...

@NgModule({
    imports: [
        FormlyPresetModule
    ]
})

b) Set the presets property

@NgModule({
    imports: [
        FormlyPresetModule,
        FormlyModule.forRoot({
        presets: [
            {
            name:'isActive',
            config: {
                key: 'isActive',
                type: 'checkbox',
                templateOptions: {
                label: 'Is Active',
                },
            }
            }
        ],
        })
    ]
})

c) Using it in component

 public fields: FormlyFieldConfig[] = [
    { type: '#isActive'},
    ...
 ];

and you will find it in the form

Figure 3

5. Field Wrappers

As you can see in the above case, the checkbox field has been put in an input field style, this is very strange, we can fix it by changing the field wrapper to formly-wrapper-div (default is formly-wrapper-mat-form-field )

a) Create a wrapper component (put all of the formly-related codes into formly folder)

//app/formly/wrappers.ts

import { Component } from '@angular/core';
import { FieldWrapper } from '@ngx-formly/core';

/**
 * This is just an example of wrapping field into a div
 */
@Component({
  selector: 'formly-wrapper-div',
  template: `
    <div>
      <ng-container #fieldComponent></ng-container>
    </div>
  `,
})
export class FormlyWrapperDivComponent extends FieldWrapper {}

as you can see, we set the selector to formly-wrapper-div and put the field container into a div

b) Add the wrappers in formly config and set the wrapper to the field, below is the preset field

FormlyModule.forRoot({
      presets: [
        {
          name:'isActive',
          config: {
            key: 'isActive',
            type: 'checkbox',
            templateOptions: {
              label: 'Is Active',
            },
            wrappers: ['div']
          }
        },
      ],
      wrappers: [
        {
          name: 'div',
          component: FormlyWrapperDivComponent,
        },
      ],
    })

c) After that, we can take a look at the result, it’s better now 🙂

Figure 4

6. Custom Field

The other powerful feature is that you can create custom fields. For example, we will create an upload file field:

a) Create the custom field component by the below command (you need to create the app/formly/custom-fields folder first)

ng g c formly/custom-fields/formly-field-file --skip-tests

b) extends the component from FieldType<FieldTypeConfig>

//formly/custom-fields/formly-field-file.component.ts

import { Component } from '@angular/core';
import { FieldTypeConfig } from '@ngx-formly/core';
import { FieldType } from '@ngx-formly/material';

@Component({
  selector: 'app-formly-field-file',
  templateUrl: './formly-field-file.component.html',
  styleUrls: ['./formly-field-file.component.scss']
})
export class FormlyFieldFileComponent extends FieldType<FieldTypeConfig> {}

c) add the below html into formly-field-file.component.html, this is the file type html template

<input type="file" accept=".pdf,.zip,.jpg,.png" [formControl]="formControl" [formlyAttributes]="field">

d) create a custom form control directive for handling the on-change event, so that can trigger the validate functions, implement ControlValueAccessor

import { Directive } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

@Directive({
  // eslint-disable-next-line
  selector: 'input[type=file]',
  host: {
    '(change)': 'onChange($event.target.files)',
    '(blur)': 'onTouched()',
  },
  providers: [
    { provide: NG_VALUE_ACCESSOR, useExisting: FileValueAccessor, multi: true },
  ],
})

export class FileValueAccessor implements ControlValueAccessor {
  value: any;
  onChange = (_: any) => {};
  onTouched = () => {};

  writeValue(_value: any) {}
  registerOnChange(fn: any) {
    this.onChange = fn;
  }
  registerOnTouched(fn: any) {
    this.onTouched = fn;
  }
}

e) add the field types into app.module

FormlyModule.forRoot({
      ...
      types: [
        {
          name: 'file',
          component: FormlyFieldFileComponent,
          wrappers: ['form-field']
        }
      ],
    })

f) create the custom validation to make sure support the correct files

export function uploadFileValidator(control: AbstractControl): ValidationErrors {
  if (control.value) {
    var fileName = control.value[0].name;
    console.log(fileName);
    var fileExt = fileName.substring(fileName.lastIndexOf('.') + 1, fileName.length).trim() || fileName;
    if (fileExt.toUpperCase() != "PDF" && fileExt.toUpperCase() != "ZIP" &&
    fileExt.toUpperCase() != "JPG" && fileExt.toUpperCase() != "PNG") {
      return { attachment: { message: 'Only support .pdf,.zip,.jpg,.png file!' } };
    }
  }
  return {};
}

apply to the field

{
  key: 'attachment',
  type: 'file',
  templateOptions: {
    required: true,
  },
  validators: {
    validation: [uploadFileValidator]
  },
},

Ok, let’s take a look at the result

Figure 5

click the file field, will popup a choose file dialog box, because we already set the input accept=".pdf,.zip,.jpg,.png", so just can see these files in the dialog box

Figure 6

if we choose another file then will be an error

Figure 7

7. Summarize

The form is very important in a website, if you need to use many forms on your site, you should use a better form package or framework to improve your work, so we learned how to use Formly in this post, but this is only a few parts of it, you can go to the official website for more details.

If you have any problems, welcome to discuss them with me :smiley:

Loading

<p>The post Using formly for creating dynamic form in Angular first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2023/08/using-formly-for-creating-dynamic-form-in-angular/feed/ 4
How to integrate HTML template into an Angular project https://www.coderblog.in/2023/08/how-to-integrate-html-template-into-an-angular-project/ https://www.coderblog.in/2023/08/how-to-integrate-html-template-into-an-angular-project/#comments Sun, 06 Aug 2023 00:54:52 +0000 https://www.coderblog.in/?p=933 1. Introduction That will be easy if we just want to simple to integrate the HTML layout into

<p>The post How to integrate HTML template into an Angular project first appeared on Coder Blog.</p>

]]>
1. Introduction

That will be easy if we just want to simple to integrate the HTML layout into Angular, just create the header,center and footer component and put the corresponding HTML to each component will be ok, but, I don’t like it, because I think the well design structure is very important for a project, in my concept, I think there are at least two part layouts for a complete website, one is frontend for presentation and the other is backend for admin management. So we should need to support two difference layouts in this situation.

By the way, for this article, I just want to demo how to create the Angular project’s infrastructure and integrate the HTML layout, so there is no any function for data operation. Ok, let’s start it!

2. About the HTML layout

Of course you can design the HTML layout yourself if you can, but I am nor a designer so I will download the existing open source and free HTML template 🙂 For this demo, I will use the free HTML template for frontend and the other one for backend.

3. Design the project infrastructure

3.1. Create base folders structure

There are several sections need to be use in this project

1) backend (admin) layout
2) authentication user login for admin management
3) frontend layout
4) core section for common logic and services
5) shared section for common components

So, we can create the below project structure

size200

and we need to handle two difference layouts, so don’t need the app.component.html and app.component.scss file, we just need to put the router-outlet in full page, so put it in the app.components.ts will be ok:

@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>',
})

3.2. Create the frontend layout

For well management and maintain, we can use module for each section. So we will create a frontend module under the frontend folder

ng g m frontend

3.2.1. Create each page for frontend

we can take a look the frontend HTML template, there are many sections in this site

Figure 1

after analyzed the HTML template, we know that we should create the below pages (components) base on frontend module, and we don’t need to generate the unit test file, so use --skip-tests parameter

ng g c frontend/header -m=frontend --skip-tests 
ng g c frontend/home -m=frontend --skip-tests 
ng g c frontend/about -m=frontend --skip-tests 
ng g c frontend/contact -m=frontend --skip-tests 
ng g c frontend/portfolio -m=frontend --skip-tests 
ng g c frontend/pricing -m=frontend --skip-tests 
ng g c frontend/services -m=frontend --skip-tests 
ng g c frontend/footer -m=frontend --skip-tests 
ng g c frontend/frontend-layout -m=frontend --skip-tests 

the lase frontend/frontend-layout is the main layout for frontend, just like the app.component.html, because we need to handle two difference layouts, so also need to create two difference main layouts.

3.2.2. Create the frontend routing

We need to create the pages routing for frontend section, for more clear and easy maintain, we create a frontend-routing.module.ts for handle the routing

import { NgModule, Component } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AboutComponent } from './about/about.component';
import { HomeComponent } from './home/home.component';
import { PortfolioComponent } from './portfolio/portfolio.component';
import { PricingComponent } from './pricing/pricing.component';
import { ServicesComponent } from './services/services.component';

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'about', component: AboutComponent },
  { path: 'home', component: HomeComponent },
  { path: 'portfolio', component: PortfolioComponent },
  { path: 'pricing', component: PricingComponent },
  { path: 'services', component: ServicesComponent }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class FrontEndRoutingModule { }

here we set the path for each page (component), so that we can use the URL for access them, and we need to add the routing to frontend.module

@NgModule({
  declarations: [
    HeaderComponent,
    HomeComponent,
    AboutComponent,
    ContactComponent,
    PortfolioComponent,
    PricingComponent,
    ServicesComponent,
    FooterComponent,
    FrontendLayoutComponent
  ],
  imports: [
    CommonModule,
    FrontEndRoutingModule  //add the routing here
  ]
})
export class FrontendModule { }

in the end, we also need to add the frontend module to app.module

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FrontendModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

3.2.3. Split the HTML to each section

We can find the HTML template structure as below

Figure 2

the <div id="wrapper">...<div> section is the dynamic content area, so we need to replace to router-outlet, and we also need to split the header and footer

so we can copy the HTML template file index.html codes into frontend-layout.component.html and replace the <div id="wrapper">...<div> to below

<div id="wrapper">
  <app-header></app-header>
  <router-outlet></router-outlet>
  <app-footer></app-footer>
</div>

and then copy the HTML codes to corresponding component, for example, copy the <header>...</header> HTML to header.component.html, and <footer>...<footer> to footer.component.html, the middle of content’s HTML between header and footer to home.component.html

Figure 3

the header and footer are static and should be fixed, so we just need to copy the middle content to each component, for example:

The middle of content about.html to about.component.html
The middle of content contact.html to contact.component.html
The middle of content portfolio.html to portfolio.component.html
The middle of content pricing.html to pricing.component.html
The middle of content services.html to services.component.html

After that, we need to copy all of the HTML template’s NoN HTML files (e.g img,css,fonts,js) into /MyDemo.Client/src/assets/frontend folder, and update all of the links in frontend-layout.component.html , so the page should be like below

Figure 4

But please wait! Because angular is not allow to put any script in the component, so if we do so, these js files will be ignored, but we also can’t put these js files into root index.html, because we need to support multiple layouts with their own javascript and css files, so how should we do?

Another approach that we can dynamic load the scripts when component loaded, let’s update frontend-layout.component.ts as below

create a function for dynamic load script

 loadScripts(path: string){
  let node = document.createElement('script');
  node.src = path;//Change to your js file
  node.type = 'text/javascript';
  node.async = true;
  node.charset = 'utf-8';
  document.getElementsByTagName('head')[0].appendChild(node);
 }

implements the OnInit event and call the loadScripts, we need to put all js files in an array and loop them

ngOnInit(): void {
    var scripts = [
      "/assets/frontend/js/jquery.js",
      "/assets/frontend/js/jquery.easing.1.3.js",
      "/assets/frontend/js/bootstrap.min.js",
      "/assets/frontend/js/portfolio/jquery.quicksand.js",
      "/assets/frontend/js/portfolio/setting.js",
      "/assets/frontend/js/jquery.flexslider.js",
      "/assets/frontend/js/animate.js",
      "/assets/frontend/js/custom.js"
    ];

    scripts.forEach(script => {
      this.loadScripts(script);
    });
 }

after that, we can use these js files, but maybe this is not the best way, so please let me know if you have other better approach 🙂

Ok, now we can try to run the project and take a look:

Figure 5

Oh, unfortunately, the page was broken, what happen? After checked it, we missed an important thing that’s update the app routing for frontend module, so update the app-routing.module.ts as below

const routes: Routes = [{
  path: '',
  component: FrontendLayoutComponent,
  children: [{
    path:'', loadChildren: () => import('./frontend/frontend.module').then(m => m.FrontendModule)
  }]
}];

it’s better now

Figure 6

but still there was a problem that the images were missed, and we can see the errors in console

Figure 7

so don’t forget to change the images path in HTML, we update the image’s path in home.component.html as below

...
<img src="assets/frontend/img/slides/1.jpg" alt="" />
...
<img src="assets/frontend/img/slides/2.jpg" alt="" />
...
<img src="assets/frontend/img/slides/3.jpg" alt="" />

For now, we review the page again, and seems good! But we still have something need to do, the Menu!

Yes, we can’t navigate to other pages without the menu. Let’s do it!

4. Add the Menu

We still need to implement the header and footer layout, just copy the <header> codes to header.component.html and don’t forget to change the image’s path, and for the page link, we need to use routerLink property, so the codes will be like

<!-- start header -->
<header>
  <div class="navbar navbar-default navbar-static-top">
    <div class="container">
      <div class="navbar-header">
        <button
          type="button"
          class="navbar-toggle"
          data-toggle="collapse"
          data-target=".navbar-collapse"
        >
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/"
          ><img src="/assets/frontend/img/logo.png" alt="logo"
        /></a>
      </div>
      <div class="navbar-collapse collapse">
        <ul class="nav navbar-nav">
          <li routerLinkActive="active"><a routerLink="home">Home</a></li>
          <li><a routerLink="about">About Us</a></li>
          <li><a routerLink="services">Services</a></li>
          <li><a routerLink="portfolio">Portfolio</a></li>
          <li><a routerLink="pricing">Pricing</a></li>
          <li><a routerLink="contact">Contact</a></li>
        </ul>
      </div>
    </div>
  </div>
</header>
<!-- end header -->

also, copy the <footer> codes into footer.component.html, these only the hardcode data, so don’t need to change anything.

After that, just review the page again and will be nice now 🙂

Figure 8

5. Summarize

Well, we tried to integrate a full HTML template to angular, there are many things need to do:

1) Design or download the HTML template
2) Split the layout to multiple components
3) Handle the multiple layouts in angular
4) Setup the routing
5) Dynamic to load the javascript files
6) Change the hyperlink in menu for support angular routing

We just demo how to integrate the frontend layout this time, actually the admin layout should be same, you can try it yourself if you are interested :satisfied:

Loading

<p>The post How to integrate HTML template into an Angular project first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2023/08/how-to-integrate-html-template-into-an-angular-project/feed/ 10
Create Paging Data from .Net Core Api https://www.coderblog.in/2023/08/create-paging-data-from-net-core-api/ https://www.coderblog.in/2023/08/create-paging-data-from-net-core-api/#comments Fri, 04 Aug 2023 00:41:04 +0000 https://www.coderblog.in/?p=929 This is post 7 of 8 in the series “Create a website with ASP.NET Core and Angular” In

<p>The post Create Paging Data from .Net Core Api first appeared on Coder Blog.</p>

]]>
  1. How to setup VS Code environment for ASP.NET Core Project
  2. Use code first to connect database in .Net Core API
  3. Use the Repository pattern in .Net core
  4. Create .Net Core Api controller for CRUD with repository
  5. Use the Serilog in .Net Core
  6. Create Angular Project and Communicate with .Net Core
  7. Create Paging Data from .Net Core Api
  8. How to do Unit Testing for the Core Api Project with Repository

This article is the second part for Create Angular Project and Communicate with .Net Core

1. Data Result Service

As we need to support filter and paging data from Api, so we also need to create a date result service for handle it. We can create this service in MyDemo.Core Api project:

1.1. Create the properties for filter and paging

//MyDemo.API/MyDemo.Core/Services/DataResultService.cs

public class DataResultService<T>
{
   #region Properties
    /// <summary>
    /// The data result.
    /// </summary>
    public List<T> Data { get; private set; }

    /// <summary>
    /// Zero-based index of current page.
    /// </summary>
    public int PageIndex { get; private set; }

    /// <summary>
    /// Number of items contained in each page.
    /// </summary>
    public int PageSize { get; private set; }

    /// <summary>
    /// Total items count
    /// </summary>
    public int TotalCount { get; private set; }

    /// <summary>
    /// Total pages count
    /// </summary>
    public int TotalPages { get; private set; }

    /// <summary>
    /// TRUE if the current page has a previous page,
    /// FALSE otherwise.
    /// </summary>
    public bool HasPreviousPage
    {
        get
        {
            return (PageIndex > 0);
        }
    }

    /// <summary>
    /// TRUE if the current page has a next page, FALSE otherwise.
    /// </summary>
    public bool HasNextPage
    {
        get
        {
            return ((PageIndex + 1) < TotalPages);
        }
    }

    /// <summary>
    /// Sorting Column name (or null if none set)
    /// </summary>
    public string? SortColumn { get; set; }

    /// <summary>
    /// Sorting Order ("ASC", "DESC" or null if none set)
    /// </summary>
    public string? SortOrder { get; set; }

    /// <summary>
    /// Filter Column name (or null if none set)
    /// </summary>
    public string? FilterColumn { get; set; }
    /// <summary>
    /// Filter Query string
    /// (to be used within the given FilterColumn)
    /// </summary>
    public string? FilterQuery { get; set; }
    #endregion
}

1.2. Create a private constructor

We need to return the data object for client side and include the above properties, so we can use a private constructor for return the data

private DataResultService(
    List<T> data,
    int count,
    int pageIndex,
    int pageSize,
    string? sortColumn,
    string? sortOrder,
    string? filterColumn,
    string? filterQuery)
{
    Data = data;
    PageIndex = pageIndex;
    PageSize = pageSize;
    TotalCount = count;
    TotalPages = (int)Math.Ceiling(count / (double)pageSize);
    SortColumn = sortColumn;
    SortOrder = sortOrder;
    FilterColumn = filterColumn;
    FilterQuery = filterQuery;
}

1.3. Get paging data

Cause I want to support dynamic filter with Linq, so we need to create a method for handle this, and need to install below packages from Nuget for support dynamic filter query

System.Linq.Dynamic.Core
System.Linq.Async

Now we can create the below method

public static async Task<DataResultService<T>> CreateAsync(
            IQueryable<T> source,
            int pageIndex,
            int pageSize,
            string? sortColumn = null,
            string? sortOrder = null,
            string? filterColumn = null,
            string? filterQuery = null)
        {
          //todo...
        }

this method can support many parameters from client side, because these parameters were passed from client by a Get method with a string format, so the first thing we need to do is split the column and query, after that, just create a dynamic SQL query pass to Linq object for get data

if (!string.IsNullOrEmpty(filterColumn)
    && !string.IsNullOrEmpty(filterQuery))
{
    if (filterColumn.Contains(","))
    {
        //handle multiple columns filter
        var index = 0;
        var sql = "";
        var queries = filterQuery.Split(',');
        foreach (var column in filterColumn.Split(','))
        {
            if (!string.IsNullOrEmpty(queries[index]) && queries[index] != "null")
            {
                sql += string.Format("{0}.Contains(@{1}) and ", column, index);
            }
            index++;
        }
        if (sql.Length > 0)
        {
            sql = sql.Substring(0, sql.Length - 4); //remove the end of 'and '
        }
        if (sql != "")
        {
            source = source.Where(sql, queries);
        }
    }
    else
    {
        source = source.Where(
        string.Format("{0}.Contains(@0)",
        filterColumn),
        filterQuery);
    }
}

get the total items and generate the data for paging

var count = await source.CountAsync();

//sort data by column 
if (!string.IsNullOrEmpty(sortColumn))
{
    sortOrder = !string.IsNullOrEmpty(sortOrder)
    && sortOrder.ToUpper() == "ASC" ? "ASC" : "DESC";

    source = source.OrderBy(string.Format("{0} {1}",sortColumn,sortOrder));
}

//get the data by paging query
source = source.Skip(pageIndex * pageSize).Take(pageSize);

//convert to list items
var data = await source.ToAsyncEnumerable().ToListAsync();

//generate the result by private constructor
return new DataResultService<T>(
            data,
            count,
            pageIndex,
            pageSize,
            sortColumn,
            sortOrder,
            filterColumn,
            filterQuery);

2. Update UserController

After created the DataResultService , we can update our UserController.

First, update the GetUsers for support paging parameters, and change the return value to DataResultService<User>

public async Task<ActionResult<DataResultService<User>>> GetUsers(
                        int pageIndex = 0, int pageSize = 10,
                        string? sortColumn = null, string? sortOrder = null,
                        string? filterColumn = null, string? filterQuery = null)

update the apiResult type to DataResultService<User>

var apiResult = new ApiResult<DataResultService<User>>();

in the end, update the get data method

var users = _userRepository.GetAll();

apiResult.Data = await DataResultService<User>.CreateAsync(
            users,
            pageIndex,
            pageSize, sortColumn,
            sortOrder, filterColumn,
            filterQuery);

3. Test the Api

Ok, we have finished the changes with our Api, now we can try it with the angular client.

First, start the Api project in VS Code console with below command (don’t forget goto the Api folder)

dotnet watch run

and create another new console and goto MyDemo.Client folder to run below command

npm run hmr

after that, you should access the user management page as below URL

http://localhost:4810/user-management

and if everything is ok, the result should be like below

Figure 1

after search by user name

Figure 2

Loading

<p>The post Create Paging Data from .Net Core Api first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2023/08/create-paging-data-from-net-core-api/feed/ 8
Create Angular Project and Communicate with .Net Core https://www.coderblog.in/2023/08/create-angular-project-and-communicate-with-net-core/ https://www.coderblog.in/2023/08/create-angular-project-and-communicate-with-net-core/#comments Wed, 02 Aug 2023 06:14:23 +0000 https://www.coderblog.in/?p=891 This is post 6 of 8 in the series “Create a website with ASP.NET Core and Angular” In

<p>The post Create Angular Project and Communicate with .Net Core first appeared on Coder Blog.</p>

]]>
  1. How to setup VS Code environment for ASP.NET Core Project
  2. Use code first to connect database in .Net Core API
  3. Use the Repository pattern in .Net core
  4. Create .Net Core Api controller for CRUD with repository
  5. Use the Serilog in .Net Core
  6. Create Angular Project and Communicate with .Net Core
  7. Create Paging Data from .Net Core Api
  8. How to do Unit Testing for the Core Api Project with Repository

We have created the ASP.NET Core Api project, the Api only for backend data handler, now we need to create the client side project to call the Api and present the website layout for end user. Here I will use Angular for client side project.

1. Why Angular

There are some of key advantages of using Angular

Component architecture – Angular promotes building applications using components. This makes the code modular, reusable and maintainable.
Declarative templates – The templates in Angular use HTML to define the UI layout declaratively. This makes it easier to visualize the UI.
Data binding – Angular handles data binding automatically between components, their templates and the application data. This reduces boilerplate code.
TypeScript support – Angular uses TypeScript which brings static typing to JavaScript. This improves code quality and maintainability.
Dependency injection – Angular comes with a built-in dependency injector that makes the code loosely coupled and easy to test.
Testing – Angular was designed with testing in mind. It has great unit and end-to-end testing support.
Cross platform – Angular can be used to build web, mobile and desktop applications from the same codebase.
Community and Google support – Angular is actively developed and maintained by Google. It also has a large community.
A lot of 3rd party extensions – There are a lot of 3rd party extensions support, this can save many of times for help us to create the project.
Features – Angular ships with a lot of built-in features like routing, forms handling, HTTP services, etc. This reduces boilerplate code.
Stable releases – The Angular team focuses on stability and quality with planned major releases every 6 months.

So, Angular simplifies web development by providing a robust framework to build declarative, component-based applications. With its strong features, community support and Google backing, it is a reliable choice for building modern web applications.

2. Create Angular Project

2.1 Install Angular

If you are first time for using Angular, you need to install the CLI, you can execute below command in VS Code terminal as below

npm install -g @angular/cli

Base on our previous Api project, so we goto Demo folder in VS Code terminal and execute below command for creating a new Angular project

ng new MyDemo.Client

After that, the folder structure should be like below

size300

2.2 Running the project

We can try to run the Angular project first, for easy debug, I will suggest using the HMR for running it, the HMR means Hot Module Replacement, which is a development feature that allows you to update the code of your running application without having to reload the entire page. This can greatly speed up the development process by allowing you to see changes in your code immediately, without having to wait for the application to rebuild and reload.

In Angular, you can enable HMR by using the ng serve command with the –hmr flag, like this:

ng serve --hmr

but for convenient to use, we can add the below command to /MyDemo.Client/package.json file under scripts section

"scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test",
    "hmr": "ng serve --hmr --disable-host-check --port 4810"
  },

and you can run the command as below for start the project

npm run hmr

after that, you will find there is an Url in console, and the port that’s what you set in the package.json

Figure 1

and you can press Ctrl + C for terminating.

3. Create an user page

3.1. Create user component

For this sample, we just need to create an user page for demonstrate how to do get data from .Net Core Api. Use the below command for create an user-management component

ng generate component user-management

it will generate 4 files in an user-management folder

user-management.component.html : the html layout for displaying
user-management.component.scss : cause we use the scss for handle the style when we create the project, so it will auto generate the scss style sheet file
user-management.component.spect.ts : the unit test file for testing the component
user-management.component.ts : you can think that’s the code-behind file for handle the business logic with typescript

by the way, if you don’t want to generate so many files (actually the unit test file should be use for a few times), you can also use the command for control it, for example the below command will skip the test and style file

ng generate component user-management  --skip-tests --inline-style 

and for easier to use, you also can use below command for the same result

ng g c user-management  --skip-tests --inline-style 

3.2 Update the routing

Update the /src/app/app-routing.module.ts, add the user management route, so that we can access from the url

const routes: Routes = [
  { path: 'user-management', component: UserManagementComponent },
];

Cause we just want a simple page for demo, so update the app.component.html , remove other html and just keep the style and base structure, put the router outlet to the main content, so we can remove all of the html code after <h2>Resources</h2> and the result should be like below

Figure 2

3.3 About the router-outlet:

The router-outlet directive is used to display the components that correspond to the current route. When a route is activated, the component that corresponds to that route is displayed within the router-outlet tag.

It’s typically included in the root component of your application, such as app.component.html. When a user navigates to a route in your application, Angular will dynamically replace the contents of the router-outlet tag with the component that corresponds to that route.

So in our case, it will show the user management component’s content, after that, you will see the below result

Figure 3

4. Create the page layout

4.1.1 Create the models

As we need to show the user data from Api, so we also need to create a same user model in client side map to Api. Create models folder under app folder, and create the user.ts model. We can use interface for the model in angular:

//MyDemo.Client/src/app/models/user.ts

/**
 * ~ User model for mapping to Api
 */
export interface User {
  id?: number;
  isActive?: boolean;
  isDeleted?: boolean;
  name?: string;
  password?: string;
  email?: string;
  createdAt?: Date;
  updatedAt?: Date;
}

and we also need to handle the Api result, so should be there is a same model for mapping

//MyDemo.Client/src/app/models/api-result.ts

/**
 * ~ The common API result model
 */
export interface ApiResult<T> {
  data?: T;
  success: boolean;
  message: string;
}

in the end, we want to show data in a table list layout (grid), so it’s need to support some paging values, we can create a model like below

//MyDemo.Client/src/app/models/data-list-result.ts

/**
 * ~The common data list result model
 */
export interface DataListResult<T> {
  data: T[];
  pageIndex: number;
  pageSize: number;
  totalCount: number;
  totalPages: number;
  sortColumn: string;
  sortOrder: string;
  filterColumn: string;
  filterQuery: string;
}

the data will be a dynamic object, so we use the generic type.

4.2. Use the extensions (or packages)

One of the advantage it there are a lot of great extensions for Angular, so we can use them to easy to create a pretty layout! For this sample, I will use @ng-matero/extensions for create the grid layout, run below command for install it

npm install @ng-matero/extensions --save

For using ng-matero/extensions, we need to add the module to app.module.ts, and because this module base on angular material, so we also need to add some material modules

//MyDemo.Client/src/app/app.module.ts

//import the base form and material modules
import { FormsModule } from '@angular/forms';

import { MatFormFieldModule } from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule} from '@angular/material/input';
import {TextFieldModule} from '@angular/cdk/text-field';

//import the grid module
import { MtxGridModule } from '@ng-matero/extensions/grid';

and set these modules into imports in @NgModule

@NgModule({
  declarations: [
    AppComponent,
    UserManagementComponent
  ],
  imports: [
    FormsModule,
    MatInputModule,
    MatButtonModule,
    TextFieldModule,
    MatFormFieldModule,
    MtxGridModule,
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

4.3. Create the user management page

4.3.1. Frontend layout

if you used the angular CLI to create the user management component, it will auto add the reference in your app.module, but we still need to set the app routing for user management

//MyDemo.Client/src/app/app-routing.module.ts

const routes: Routes = [
  { path: 'user-management', component: UserManagementComponent },
];

after that, we can use the url http://localhost:4801/user-management to access.

Ok, let’s update the user management page layout. We will use the material theme for the layout and ng-matero/extensions grid for showing data, so the frontend html codes should be like below

<!-- user-management.component.html -->

<h1>User Management </h1>

<div class="row">
  <div class="col-sm-12">
      <!-- use the material form field and button -->
      <mat-form-field class="col-sm-3">
        <input matInput [(ngModel)]="query.q" name="q" (keyup.enter)="search()" placeholder="Filter by user name">
      </mat-form-field>
      <button class="m-r-8 bg-green-700 text-light" mat-raised-button  (click)="search()">Search</button>
      <button class="m-r-8 bg-red-700 text-light" mat-raised-button (click)="reset()">Reset</button>
  </div>
</div>
<p></p>

<!-- use the ng-matero grid -->
<mtx-grid [data]="list"
          [columns]="columns"
          [length]="total"
          [loading]="isLoading"
          [hideRowSelectionCheckbox]="false"
          [multiSelectable]="true"
          [rowSelectable]="true"
          [columnResizable]="true"
          [columnSortable] = "true"
          [pageOnFront]="false"
          [toolbarTitle]="'Show Columns'"
          [showToolbar]="true"
          [rowHover]="true"
          [rowStriped]="true"
          [pageIndex]="query.page"
          [pageSize]="query.per_page"
          [pageSizeOptions]="[5,10,20]"
          (page)="getNextPage($event)"
          (sortChange)="sortingChange($event)"
          [sortOnFront] = "false">
</mtx-grid>

you can goto here for the details how to use the grid.

4.3.2. Backend grid

We also need to update the backend ts file

//user-management.component.ts

//import the grid and material paginator, we need to support paging
import { PageEvent } from '@angular/material/paginator';
import { MtxGridColumn } from '@ng-matero/extensions/grid';

define the variable for paging default values

public defaultFilterColumn: string = "name";
public defaultPageIndex: number = 0;
public defaultPageSize: number = 10;
public defaultSortColumn: string = "name";
public defaultSortOrder: "asc" | "desc" = "asc";
public isLoading = true;
public list: any[] = []; //model listing data
public total = 0;   //the total records
public filterQuery?: string; //filter query for search 

define the grid’s column, it will be apply to frontend html value

public columns: MtxGridColumn[] = [ 
  { header: 'Id', field: 'id', hide: true },
  { header: 'Active', field: 'isActive', sortable: true,
      type: 'tag',
      tag: {
        true: { text: 'Yes', color: 'red-100' },
        false: { text: 'No', color: 'green-100' },
      },
  },
  { header: 'User Name', field: 'name', sortable: true, minWidth: 250 },
  { header: 'Email', field: 'email', minWidth: 250 ,sortable: true },
  { header: 'Created Date', field: 'createdAt' , minWidth: 150, sortable: true},
  { header: 'Updated Date', field: 'updatedAt' , minWidth: 160, sortable: true},
];

you can see the column field name should be same with the user model’s field name.

4.3.3. Create service

For support get data from Api, we can create a service for handle it. Create a services folder and user-management.service.ts as below

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { debounceTime, Observable, tap } from 'rxjs';
import { DataListResult } from '../models/data-list-result';
import { ApiResult } from '../models/api-result';
import { User } from '../models/user';

@Injectable({
  providedIn: 'root',
})
export class UserManagementService {

  constructor(public http: HttpClient) { }

  public getData(
    pageIndex: number,
    pageSize: number,
    sortColumn: string,
    sortOrder: string,
    filterColumn: string | null,
    filterQuery: string | null
  ): Observable<ApiResult<DataListResult<User>>> {
    var url = "http://localhost:5293/api/UserManagement/users";

    //pass the params to api for get data
    var params = new HttpParams()
      .set("pageIndex", pageIndex.toString())
      .set("pageSize", pageSize.toString())
      .set("sortColumn", sortColumn)
      .set("sortOrder", sortOrder);
    if (filterColumn && filterQuery) {
      params = params
        .set("filterColumn", filterColumn)
        .set("filterQuery", filterQuery);
    }

    //call the api and return the result map to DataListResult and User model
    return this.http.get<ApiResult<DataListResult<User>>>(url, { params });
  }
}

As you can see, we need to pass many parameters to Api for support paging, and the Api should return the DataListResult<T> object. But for the previous article, our user Api didn’t support these parameters, don’t worry, we will update the Api later, for now , let’s complete the user-management.component.ts first!

4.3.4. Handle get data and filter methods

For using the user service, we need to pass it into constructor, so update the user-management.component.ts as below

constructor(private userManagementService: UserManagementService,
      private dialog: MtxDialog) { }

and also use the MtxDialog for showing error messages.

Next, we need to define a query object for save the filter and paging data

public query = {
  q: '',
  sort: 'name',
  order: 'desc',
  page: 0,
  per_page: this.defaultPageSize,
};

and then, we can create a getData call the service method

public getData() {
  //get sort column
  var sortColumn = (this.query.sort)
    ? this.query.sort
    : this.defaultSortColumn;
  //get the order method
  var order = (this.query.order)
    ? this.query.order
    : this.defaultSortOrder;
  //get the filter query
  var filterQuery = (this.query.q)
    ? this.query.q
    : null;
  //call service to get data from Api
  this.userManagementService.getData(
    this.query.page,
    this.query.per_page,
    sortColumn,
    order,
    this.defaultFilterColumn,
    filterQuery).subscribe(res => {
        //console.log('res', res);
        if(res.success){
          this.list = res.data ? res.data.data : [] ;
          this.total = res.data? res.data.totalCount : 0;
        } else {
          this.dialog.alert(`Failed to get data!`);
        }
      }
    );
}

after that, we can handle the search and paging methods

public search() {
  this.query.page = 0;
  this.getData();
}
public reset() {
  this.query.q = '';
  this.query.page = 0;
  this.getData();
}
//goto next page event
public getNextPage(e: PageEvent) {
  this.query.page = e.pageIndex;
  this.query.per_page = e.pageSize;
  this.getData();
}
//sorting change event
public sortingChange(e: Sort) {
    //console.log('sortingChange', e);
    this.query.sort = e.active;
    this.query.order = e.direction;
    this.getData();
}
//for load data when first time access the page
public loadData(query?: string) {
  var pageEvent = new PageEvent();
  pageEvent.pageIndex = this.defaultPageIndex;
  pageEvent.pageSize = this.defaultPageSize;
  this.filterQuery = query;
  this.getData();
}
//load data when access the page
public ngOnInit() {
  this.loadData();
}

So far, we finished the client side codes, we can just take a look the page

Figure 4

Ok, I know, there is a last important thing we still need to do, otherwise we can’t get any data from our Api. But because there are also many things need to do, so I will describe the details in the next article 🙂

Loading

<p>The post Create Angular Project and Communicate with .Net Core first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2023/08/create-angular-project-and-communicate-with-net-core/feed/ 9
How to communicate between ASP.NET Core and Angular https://www.coderblog.in/2023/02/how-to-communicate-between-asp-net-core-and-angular/ https://www.coderblog.in/2023/02/how-to-communicate-between-asp-net-core-and-angular/#comments Sat, 18 Feb 2023 08:57:11 +0000 https://www.coderblog.in/?p=740 ASP.NET Core and Angular are two popular technologies used in web development. While they are great on their

<p>The post How to communicate between ASP.NET Core and Angular first appeared on Coder Blog.</p>

]]>
ASP.NET Core and Angular are two popular technologies used in web development. While they are great on their own, integrating them together can create a powerful web application. In order to integrate these two technologies, we need to be able to communicate between them. In this post, we will discuss the various ways in which we can communicate between ASP.NET Core and Angular.

  1. HTTP Client
    One way to communicate between ASP.NET Core and Angular is to use the built-in HTTP Client in Angular. This allows us to make HTTP requests to the server and get data back in JSON format. To use the HTTP Client, we need to import it from the ‘@angular/common/http’ package. Here is an example of how to use the HTTP Client in Angular:
import { HttpClient } from '@angular/common/http';

export class MyService {
    constructor(private http: HttpClient) {}

    getData() {
        return this.http.get('/api/data');
    }
}
  1. WebSockets
    WebSockets provide a real-time, two-way communication channel between the client and server. This allows us to push data from the server to the client without the client needing to make a request. To use WebSockets, we need to use a WebSocket library on both the client and server side. Here is an example of how to use WebSockets in Angular:
import { Injectable } from '@angular/core';
import * as io from 'socket.io-client';

@Injectable()
export class MyService {
    private socket: SocketIOClient.Socket;

    constructor() {
        this.socket = io();
    }

    getData() {
        return new Observable(observer => {
            this.socket.on('data', data => {
                observer.next(data);
            });
        });
    }
}
  1. SignalR
    SignalR is a library that provides real-time functionality for web applications. It provides an easy-to-use API for creating real-time applications. To use SignalR, we need to install the Microsoft.AspNetCore.SignalR NuGet package on the server side and the @aspnet/signalr package on the client side. Here is an example of how to use SignalR in ASP.NET Core and Angular:
// Server-side
public class MyHub : Hub
{
    public async Task SendData(string data)
    {
        await Clients.All.SendAsync("data", data);
    }
}

// Client-side
import { Injectable } from '@angular/core';
import * as signalR from '@aspnet/signalr';

@Injectable()
export class MyService {
    private connection: signalR.HubConnection;

    constructor() {
        this.connection = new signalR.HubConnectionBuilder()
            .withUrl('/myHub')
            .build();
        this.connection.start();
    }

    getData() {
        return new Observable(observer => {
            this.connection.on('data', data => {
                observer.next(data);
            });
        });
    }
}

In conclusion, there are several ways to communicate between ASP.NET Core and Angular. The choice of which method to use will depend on the requirements of the application. The HTTP Client is suitable for simple data requests, while WebSockets and SignalR are better suited for real-time applications.

Loading

<p>The post How to communicate between ASP.NET Core and Angular first appeared on Coder Blog.</p>

]]>
https://www.coderblog.in/2023/02/how-to-communicate-between-asp-net-core-and-angular/feed/ 15