Εμφάνιση αναρτήσεων με ετικέτα Material. Εμφάνιση όλων των αναρτήσεων
Εμφάνιση αναρτήσεων με ετικέτα Material. Εμφάνιση όλων των αναρτήσεων

Τρίτη 2 Ιουνίου 2020

Angular Material Form (Mat-Error Validation and Mat-Datepicker)








Angular Material Form (Mat-Error Validation and Mat-Datepicker)



 Form fields with submit button:

Form validation (on submit):

Material Datepicker:








1. Component - HTML


<div class="user-form">
    <form role="form" (ngSubmit)="submit()" [formGroup]="userForm" autocomplete="off">
        <h1 class="description" fxLayoutAlign="space-evenly center">
            Please fill all below fields
        </h1>
        <br/>
        <div>
            <mat-form-field hideRequiredMarker class="full-width">
                <mat-label class="form-label">Request UID</mat-label>
                <input class="field-value" readonly matInput name="uid" id="uid" type="text"
                       value="{{uid}}">
            </mat-form-field>
            <br/><br/>
            <mat-form-field hideRequiredMarker class="full-width">
                <mat-label class="form-label">Name</mat-label>
                <input class="field-value" matInput name="name" id="name" formControlName="name" type="text">
                <mat-error *ngIf="userForm.get('name')?.errors">Please enter your full name
                </mat-error>
            </mat-form-field>
            <br><br/>
            <mat-form-field hideRequiredMarker class="full-width">
                <mat-label class="form-label">Street</mat-label>
                <input class="field-value" matInput name="street" id="street" formControlName="street" type="text">
                <mat-error *ngIf="userForm.get('street')?.errors">Please enter your street
                    address
                </mat-error>
            </mat-form-field>
            <br><br/>
            <mat-form-field hideRequiredMarker class="full-width">
                <mat-label class="form-label">Post code</mat-label>
                <input class="field-value" matInput name="postcode" id="postcode" formControlName="postcode" type="text">
                <mat-error *ngIf="userForm.get('postcode')?.errors">Please enter your postal
                    code
                </mat-error>
            </mat-form-field>
            <br><br/>
            <mat-form-field hideRequiredMarker class="full-width">
                <mat-label class="form-label">City</mat-label>
                <input class="field-value" matInput name="city" id="city" formControlName="city" type="text">
                <mat-error *ngIf="userForm.get('city')?.errors">Please enter your city
                </mat-error>
            </mat-form-field>
            <br><br/>
            <mat-form-field hideRequiredMarker class="full-width">
                <mat-label class="form-label">Birth Date</mat-label>
                <input class="field-value" readonly matInput [matDatepicker]="datepicker" [formControl]="userBirthDate" name="birthdate"
                       id="birthdate" formControlName="birthdate"
                       placeholder="{{'oneapp.components.contractTermination.birthdate' | translate}}">
                <mat-datepicker-toggle matSuffix [for]="datepicker"></mat-datepicker-toggle>
                <mat-datepicker touchUi #datepicker></mat-datepicker>
                <mat-error *ngIf="userForm.get('birthdate')?.errors">Please enter your
                    birthdate
                </mat-error>
            </mat-form-field>
            <br>
        </div>
        <br>
        <div>
            <button mat-button>Submit</button>
        </div>
    </form>
</div>



2. Component - TS


import {Component} from '@angular/core';
import {FormBuilder, FormControl, Validators} from '@angular/forms';
import {register} from 'app/oneapp/shared/util/component-mapping';
import {IUserFormRequest} from 'app/oneapp/shared/model/contract-termination.model';
@Component({
  selector: 'jhi-user-form',
  templateUrl: './user-form.component.html',
  styleUrls: ['./user-form.component.scss']
})
@register
export class UserFormComponent  {
  uid = 'AAd566HIi0009001';
  userName = 'John Doe';
  userStreet = 'Main str. 12';
  userPostCode = '54351';
  userCity = 'Washington';
  userBirthDate = new FormControl(new Date());
  userForm = this.fb.group({
    name: ['', [Validators.required]],
    street: ['', Validators.required],
    postcode: ['', Validators.required],
    city: ['', Validators.required],
    birthdate: ['', Validators.required]
  });
  constructor(
    private fb: FormBuilder
  ) {
    this.userForm.get('name')!.setValue(this.userName);
    this.userForm.get('street')!.setValue(this.userStreet);
    this.userForm.get('postcode')!.setValue(this.userPostCode);
    this.userForm.get('city')!.setValue(this.userCity);
    this.userForm.get('birthdate')!.setValue(this.userBirthDate);
  }
  submit(): void {
    // Perform birthdate field validation before submitting
    this.userForm.controls['birthdate'].updateValueAndValidity();
    if (!this.userForm.invalid) {
      const fullRequest: IUserFormRequest = this.constructRequest();
    }
  }
  /**
   * Prepare the request object
   *
   */
  private constructRequest(): IUserFormRequest {
    const request: IUserFormRequest = {
      uid: this.uid,
      user: this.userForm.value
    };
    return request;
  }
}




3. Component - CSS



.full-width {
  width: 100%;
}

.user-form {
  height: 100%;
  padding: 2.7vw;
}

mat-form-field.mat-form-field {
  font-size: 2.08vh;
  line-height: 2.6vh;
}

.mat-button {
  border-radius: 30px;
  font-size: 2.08vh;
  background-color: #ea485d;
  color: white;
  width: 100%;
}

.description {
  font-weight: 100;
  font-size: 2.1vh;
}

.field-label {
  color: lightgray;
  font-size: 1.5vh;
  margin-bottom: 0.3vh;
}

.form-label {
  color: lightgray;
  font-size: 2.3vh;
  font-weight: 500;
  margin-bottom: 1.3vh;
}

.field-value {
  font-weight: 500;
  font-size: 2.1vh;
}




Κυριακή 3 Μαΐου 2020

Angular Bootstrap Navigation/Tab pills








Tab navigation with Bootstrap, ngSwitch and Material




Prerequisites:
bootstrap v4.3.1
@angular/material v8.2.3




1. Component - HTML


<div class="tab-page">
    <div fxLayout="row" fxLayoutAlign="center start">
        <div>
            <ul class="nav nav-pills">
                <li><a [class.nav-link]="true" [class.active]="viewMode=='tab1'" (click)="viewMode='tab1'">Tab-1</a></li>
                <li><a [class.nav-link]="true" [class.active]="viewMode=='tab2'" (click)="viewMode='tab2'">Tab-2</a></li>
                <li><a [class.nav-link]="true" [class.active]="viewMode=='tab3'" (click)="viewMode='tab3'">Tab-3</a></li>
            </ul>

            <mat-divider></mat-divider>

            <div [ngSwitch]="viewMode">
                <div *ngSwitchCase="'tab1'"> Tab 1 content...</div>
                <div *ngSwitchCase="'tab2'"> Tab 2 content...</div>
                <div *ngSwitchCase="'tab3'"> Tab 3 content...</div>
            </div>
        </div>
    </div>


</div>


2. Component - TS


import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'tabs-page',
  templateUrl: './tabs-page.component.html',
  styleUrls: ['./tabs-page.component.scss']
})
export class TabsPageComponent implements OnInit {

  viewMode: string;

  constructor() { }

  ngOnInit() {
  }

}



3. Component - CSS


.nav-pills {
  font-size: 30px;
}

.tab-page{
  height: 500px;
}

Τετάρτη 22 Απριλίου 2020

Angular - Redirect to Component with pop-up Material Dialog






Show Component with MatDialog service as popup



Prerequisites:
@angular/material v8.2.3







1. Redirection Service - TS


...
  constructor(public dialog: MatDialog, private location: PlatformLocation, public translate: TranslateService) {
    this.location.onPopState(() => {
      const length = this.dialog.openDialogs.length;
      if (!this.ignorePop) {
        if (length > 0) {
          this.dialog.openDialogs[length - 1].componentInstance.closePopup();
        }
      } else {
        this.ignorePop = false;
      }
    });
  }

// Method to open component 
  open(title: string, componentType: string, options?: { [key: string]: any }): void {
    let translate = this.translate.instant(title);
    if (translate.includes('[' + title + ']')) {
      translate = title;
    }
    const dialogRef = this.dialog.open(RedirectionDialogComponent, {
      data: { title: translate, component: getComponent(componentType)},
      maxWidth: '90%',
      width: '90%',
      height: 98.5 - this.dialog.openDialogs.length * 1.5 + '%',
      hasBackdrop: false,   
     closeOnNavigation: false,
    });


  }
.......



2. Redirection Dialog Component - TS


export interface DialogData {

  title: string;

  component: Type<any>;

  parameters: { [key: string]: any };
}

@Component({
  selector: 'redirection-dialog',
  templateUrl: './redirection-dialog.component.html',
  styleUrls: ['./redirection-dialog.component.scss']
})
export class RedirectionDialogComponent implements OnInit {

  @ViewChild('contentComponent', { static: true, read: ViewContainerRef }) contentComponent!: ViewContainerRef;

  constructor(
    public dialogRef: MatDialogRef<RedirectionDialogComponent >,
    @Inject(MAT_DIALOG_DATA) public data: DialogData,
    public dialog: MatDialog,
    private componentFactoryResolver: ComponentFactoryResolver,
    private el: ElementRef
  ) {}

  ngOnInit(): void {
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.data.component);
    this.contentComponent.clear();
    const compRef = this.contentComponent.createComponent(componentFactory);
  }
}


3. Redirection Dialog Component - HTML

<div>
    <h1> {{data.title}} </h1>
<mat-dialog-content>
    <div #contentComponent></div>
</mat-dialog-content>
</div>

3.1 Example of use:

In your code:
...
this.redirectionService.open('Your component title', 'YourComponent');
...

Πέμπτη 16 Απριλίου 2020

Angular Material accordion/expansion deletable panels






Panels/Cards in Accordion :



Cards layout (also deletable)
Open first panel:


Open second panel: 




Prerequisites:
@angular/material v8.2.3




1. Component - HTML


         <div *ngIf="banks.length" class="bank-accounts-overview">
                <mat-accordion>
                    <div *ngFor="let bank of banks; let i = index;">
                        <div id="id{{i}}">
                            <mat-expansion-panel hideToggle="true" #xyz>
                                <mat-expansion-panel-header [collapsedHeight]="'12vh'" [expandedHeight]="'12vh'">
                                    <mat-panel-title>
                                        <div class="bank-header-entry">
                                            <div class="bank-entry" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="2.31vw">
                                                <span>{{bank.bankName}}</span>
                                                <span class="balance">{{bank.totalBalance}} €</span>
                                            </div>
                                        </div>
                                    </mat-panel-title>
                                    <mat-panel-description>
                                        <div style="position:absolute; right:0; margin-right: 10px;">
                                        <button (click)=deleteCard(i) type="button" mat-icon-button matSuffix>
                                            <mat-icon>delete_forever</mat-icon>
                                        </button>
                                        </div>
                                    </mat-panel-description>
                                </mat-expansion-panel-header>
                                <div *ngFor="let account of bank.accounts">
                                    <mat-divider></mat-divider>
                                    <div class="jhi-bank-account-entry" matRipple>
                                        <div class="bank-account-entry" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="2.70vw">
                                            <mat-icon>arrow_forward</mat-icon>
                                            <span>{{account.accountName}}</span>
                                            <span class="balance">{{account.balance}} € </span>
                                        </div>
                                    </div>
                                </div>

                            </mat-expansion-panel>
                        </div>
                        <br/>
                    </div>
                </mat-accordion>

            </div>




2. Component - TS


@Component({
  selector: 'accounts-overview',
  templateUrl: './accounts-overview.component.html',
  styleUrls: ['./accounts-overview.component.scss']
})
export class AccountsOverviewComponent implements OnInit {
  banks: any[] = [];

  constructor(private renderer: Renderer2) {

    this.banks = [
      {
        bankName: 'Bank 1',
        icon: 'account_box',
        totalBalance: 214000,
        hide: false,
        accounts: [{accountId:'1',bankName:'Bank 1',icon:'account_box',accountName:'Account 1',balance:20000},
          {accountId:'1',bankName:'Bank 1',icon:'account_box',bankLoginId:'idxxx',accountName:'Account 1',balance:432000,iban:'XXXA4RRT5',bic:'Bic',hidden:false}]
      },
      {
        bankName: 'Bank 2',
        icon: 'account_box',
        totalBalance: 1645000,
        hide: false,
        accounts: [{accountId:'1',bankName:'Bank 1',icon:'account_box',accountName:'Account 1',balance:17000},
          {accountId:'1',bankName:'Bank 1',icon:'account_box',bankLoginId:'idxxx',accountName:'Account 1',balance:4.000,iban:'XXXA4RRT5',bic:'Bic',hidden:false}]
      },
      {
        bankName: 'Bank 3',
        icon: 'account_box',
        totalBalance: 145.000,
        hide: false,
        accounts: [{accountId:'1',bankName:'Bank 1',icon:'account_box',accountName:'Account 1',balance:150.000},
          {accountId:'1',bankName:'Bank 1',icon:'account_box',bankLoginId:'idxxx',accountName:'Account 1',balance:12.000,iban:'XXXA4RRT5',bic:'Bic',hidden:false}]
      }
    ];
  }

  ngOnInit(): void {
  }

  deleteCard(id: number): void {
    const parent: HTMLElement | null = document.getElementById('id'+id);
    if (parent !== null) {
      const child = parent.children[0];
      this.renderer.removeChild(parent, child);
    }
  }
}



3. Component - CSS


.bank-header-entry {
  width: 80vw;
}

.bank-accounts-overview {
  padding: 1.5vh;
}

.jhi-bank-account-entry {
  padding-top: 3.9vh;
  padding-bottom: 3.9vh;
  padding-left: 30px;
}

button.mat-raised-button {
  height: 6.25vh;
  font-size: 2.08vh;
  line-height: 2.6vh;
  border-radius: 0;
}

h1 {
  font-size: 2.92vh;
  line-height: 3.64vh;
  text-align: center;
  width: 100%;
}

@media (hover: hover) {
  .jhi-bank-entry:hover,
  .jhi-bank-account-entry:hover {
    background-color: #d2d2d2;
  }
}


.bank-entry {
  width: 100%;
}

.logo {
  width: 5.21vh;
  height: 5.21vh;
}

span {
  font-size: 2.08vh;
  line-height: 2.6vh;
  font-weight: bold;
}

.balance {
  margin-left: auto;
}

.bank-account-entry {
  width: 90%;
}





Τετάρτη 8 Απριλίου 2020

(Material Tab Group) Custom Expand/Collapse mat-card







Custom Expand/Collapse mat-card (Mat-tab-group case)



After clicking down arrow:


After clicking down arrow again, show 1st screen.




Prerequisites:
@angular/material v8.2.3






1. Container component - html


 <mat-tab-group mat-stretch-tabs [dynamicHeight]="true" [backgroundColor]="'#fff'">

    <mat-tab label="Tab 1">
        Content 1 <br><br><br> some content...
    </mat-tab>
    <mat-tab label="Tab 2">
        <some-component></some-component>
        <jhi-your-card></jhi-your-card>
    </mat-tab>
</mat-tab-group>


2. Card Component - html


<div  [ngStyle]="myCardStyle" class="main-container" style="color: #1f69c0">
    <mat-card>
        <div>

            <div *ngIf="isExpanded" fxLayout="row" fxLayoutAlign="start center">
                <button (click)=collapseCard() type="button" mat-icon-button matSuffix style="color: #1f69c0">
                    <mat-icon>keyboard_arrow_down</mat-icon>
                </button> Close
            </div>

            <div class="some-label">
                Label 1
            </div>
            <div fxLayout="row" fxLayoutAlign="start center" class="margin">
                <div class="comp-stuff" fxFlex="80" style="color: #1f69c0">
                    Component stuff
                </div>
            </div>
            <div *ngIf="!isExpanded" class="bottom-links margin" fxLayout="row" fxLayoutAlign="start end">
                <div fxFlex="53"></div>
                <div fxFlex="47" fxLayoutAlign="end">
                    <button mat-button class="bottom-links full-width">Click Me
                    </button>
                </div>
            </div>

            <div *ngIf="isExpanded" class="bottom-links margin" fxLayout="row" fxLayoutAlign="start end">
                <div fxFlex="53"></div>
                <div fxFlex="47" fxLayoutAlign="end">
                    <button mat-button class="bottom-links full-width">Click Me 1
                    </button>
                </div>
            </div>
            <div *ngIf="isExpanded" class="bottom-links margin" fxLayout="row" fxLayoutAlign="start end">
                <div fxFlex="53"></div>
                <div fxFlex="47" fxLayoutAlign="end">
                    <button mat-button class="bottom-links full-width">Click Me 2
                    </button>
                </div>
            </div>
            <div *ngIf="isExpanded" class="bottom-links margin" fxLayout="row" fxLayoutAlign="start end">
                <div fxFlex="53"></div>
                <div fxFlex="47" fxLayoutAlign="end">
                    <button mat-button class="bottom-links full-width">Click Me 3
                    </button>
                </div>
            </div>
            <div *ngIf="isExpanded" class="bottom-links margin" fxLayout="row" fxLayoutAlign="start end">
                <div fxFlex="53"></div>
                <div fxFlex="47" fxLayoutAlign="end">
                    <button mat-button class="bottom-links full-width">Click Me 4
                    </button>
                </div>
            </div>
        </div>

        <br/>

        <div *ngIf="!isExpanded" fxLayout="row" fxLayoutAlign="center center">
            <button (click)=expandCard() type="button" mat-icon-button matSuffix style="color: #1f69c0">
                <mat-icon>keyboard_arrow_down</mat-icon>
            </button>
        </div>

    </mat-card>
</div>


3. Card component - CSS

.main-container {
  padding: 5px;
  width: 100%;
}

div.card-content {
  overflow: hidden;
}

.full-width {
  width: 100%;
}

.button-margin {
  margin-right: 1.5vh;
}

mat-icon {
  margin-left: 1vw;
  margin-top: 1vw;
  font-size: 2.5vh;
}

mat-card {
  margin-left: 1.5vw;
  background-color: white;
  width: 97%;
  margin-bottom: 11vh;
  padding: 3.5vh;
  overflow: scroll;
}

.free-money-label {
  font-size: 2.08vh;
  color: lightgray;
}

.mat-button {
  background-color: darkgray;
  border-radius: 20px;
}

.jhi-last-transaction-entry {
  padding-top: 1.56vh;
  padding-bottom: 1.56vh;
  margin-bottom: 1.5vh;
  margin-top: 1.5vh;
}

.transactions-button {
  font-size: 2.08vh;
  color: #ea485d;
}

.arrow-button {
  img {
    width: 5vw;
    height: 5vw;
  }
}

.free-money {
  font-size: 4.48vh;
  color: #ea485d;
}

.bottom-links {
  font-size: 2.08vh;
  color: white;
}

.margin {
  margin-bottom: 1.5vh;
}

button {
  overflow-x: visible !important;
}


4. Card Component - TS


import {Component, OnInit} from '@angular/core';

@Component({
  selector: 'jhi-your-card',
  templateUrl: './your-card.component.html',
  styleUrls: ['./your-card.component.scss']
})
export class YourCardComponent implements OnInit {

  public myCardStyle = {};

  isExpanded = false;

  expandedCardStyle = {
    ['position']: 'fixed',
    ['z-index']: '1001',
    ['top']: '0',
    ['margin-top']: '13vh',
    ['left']: '0',
    ['width']: '100%',
    ['height']: '80%',
    ['overflow']: 'scroll'
  };

  constructor() {}

  ngOnInit(): void {}

  expandCard(): void {
    this.myCardStyle = this.expandedCardStyle;
    this.isExpanded = true;
  }

  collapseCard(): void {
    this.myCardStyle = {};
    this.isExpanded = false;
  }
}


Δευτέρα 6 Απριλίου 2020

Custom Expand/Collapse mat-card with ngStyle,CSS







Custom Material Card expand/collapse


After clicking down arrow -->


After clicking down arrow again, show 1st screen.




Prerequisites:
@angular/material v8.2.3




1. Container component - html



<div style="height: 33vh;"  fxLayout="row" fxLayoutAlign="center center">
        <p> Some component....</p>
</div>
<jhi-your-card></jhi-your-card>


2. Card component - html


<div  [ngStyle]="myCardStyle" class="main-container" style="color: #1f69c0">
    <mat-card>
        <div>

            <div *ngIf="isExpanded" fxLayout="row" fxLayoutAlign="start center">
                <button (click)=collapseCard() type="button" mat-icon-button matSuffix style="color: #1f69c0">
                    <mat-icon>keyboard_arrow_down</mat-icon>
                </button> Close
            </div>

            <div class="some-label">
                Label 1
            </div>
            <div fxLayout="row" fxLayoutAlign="start center" class="margin">
                <div class="comp-stuff" fxFlex="80" style="color: #1f69c0">
                    Component stuff
                </div>
            </div>
            <div *ngIf="!isExpanded" class="bottom-links margin" fxLayout="row" fxLayoutAlign="start end">
                <div fxFlex="53"></div>
                <div fxFlex="47" fxLayoutAlign="end">
                    <button mat-button class="bottom-links full-width">Click Me
                    </button>
                </div>
            </div>
        </div>

        <br/>

        <div *ngIf="!isExpanded" fxLayout="row" fxLayoutAlign="center center">
            <button (click)=expandCard() type="button" mat-icon-button matSuffix style="color: #1f69c0">
                <mat-icon>keyboard_arrow_down</mat-icon>
            </button>
        </div>

    </mat-card>
</div>



3. Card component - css 


.main-container {
  padding: 5px;
  display: flex;
  justify-content: center;
  position: absolute;
  width: 100%;
}
.full-width {
  width: 100%;
}
.button-margin {
  margin-right: 1.5vh;
}
mat-icon {
  margin-left: 1vw;
  margin-top: 1vw;
  font-size: 2.5vh;
}
mat-card {
  background-color: white;
  width: 95%;
  margin-bottom: 11vh;
  padding: 3.5vh;
}
.some-label {
  font-size: 2.08vh;
  color: lightgray;
}
.mat-button {
  background-color: darkgray;
  border-radius: 20px;
}
.comp-stuff {
  font-size: 4.48vh;
}
.bottom-links {
  font-size: 2.08vh;
  color: white;
}
.margin {
  margin-bottom: 1.5vh;
}
button {
  overflow-x: hidden !important;
}



4. Card component - ts


import {Component, OnInit} from '@angular/core';

@Component({
  selector: 'jhi-your-card',
  templateUrl: './your-card.component.html',
  styleUrls: ['./your-card.component.scss']
})
export class YourCardComponent implements OnInit {

  public myCardStyle = {};

  isExpanded = false;

  expandedCardStyle = {
    ['position']: 'absolute',
    ['z-index']: '10',
    ['top']: '0',
    ['left']: '0',
    ['width']: '100%',
    ['height']: '90%'
  };

  constructor() {}

  ngOnInit(): void {}

  expandCard(): void {
    this.myCardStyle = this.expandedCardStyle;
    this.isExpanded = true;
  }

  collapseCard(): void {
    this.myCardStyle = {};
    this.isExpanded = false;
  }
}



Κυριακή 5 Απριλίου 2020

Display paginated data chronologically with Angular (infinite scrolling)





  •  Display of data elements by time order. 
  • Scrolling down triggers month change in upper bar.
  • Clicking on a month triggers display of related elements




Prerequisites:

- Your backend should provide paginated data (see here how to)
- @angular/cdk": "~8.2.3" 
- @angular/material v8.2.3







1. Component - html



<div class="element-list " fxLayout="column">
  <div class='month-navigation'>
    <a class='monthView' *ngFor="let monthName of months; let i = index"
       (click)="monthClickAction(i)"  [style.left]="defineMoveToLeft(i)">
      {{monthName}}
    </a>
  </div>

  <cdk-virtual-scroll-viewport mat-list itemSize="60" minBufferPx="240" maxBufferPx="480"
    class="transaction-scrolling-viewport" role="list" (scrolledIndexChange)=scrollIndexChangeAction($event) fxFlex="grow">

<!-- See step 5 -->
  <jhi-element-entry *cdkVirtualFor="let element of elementsToShow"
  [icon]="'grade'" [date]="element.date" [amount]="element.amount"
  [mainText]="element.elementName">
  </jhi-element-entry>

    <div class="load-item" >
      <div class="content">
        <div class="text">Loading elements..</div>
        <mat-progress-bar mode="indeterminate"></mat-progress-bar>
      </div>
    </div>

  </cdk-virtual-scroll-viewport>


</div>



2. Component - ts


import {Component, Input, OnInit, ViewChild} from '@angular/core';
import {FormBuilder} from '@angular/forms';
import {IElement} from '../model/your-element.model';
import {CdkVirtualScrollViewport} from '@angular/cdk/scrolling';
import {YourService} from '../services/yourApp.service';
import {HttpErrorResponse, HttpResponse} from '@angular/common/http';
import {HttpPage} from '../model/page.model';

@Component({
  selector: 'jhi-element-list',
  templateUrl: './element-list.component.html',
  styleUrls: ['./element-list.component.scss']
})
export class ElementListComponent implements OnInit {
  @Input()
  public showMonthBar = true;
// See step 7
  private elements: IElement[] = [];
  public elementsToShow: IElement[] = [];

  // See step 4 for this page model
  private page: HttpPage = {number: 0, size: 40, totalPages: 0, totalElements: 0};

  private readonly curMonth: number;
  private readonly curYear: number;
  private activeYear: number;
  indexOfActiveMonth: number;
  private lastIndex = 0;

  @ViewChild(CdkVirtualScrollViewport, {static: true})
  scrollViewPort!: CdkVirtualScrollViewport;

  months = [
    'january',
    'february',
    'march',
    'april',
    'may'
  ];

  constructor(
    private fb: FormBuilder,
    private yourService: YourService
  ) {
    const date = new Date();

    this.curMonth = date.getMonth();
    this.curYear = date.getFullYear();
    this.activeYear = date.getFullYear();
    this.indexOfActiveMonth = date.getMonth();
  }

  ngOnInit(): void {
    this.getElements(0, false);
  }

  getElements(pageNumber: number, doIncrement: boolean): void {
// See step 6
    this.yourService.getElementsByPageAndSize(pageNumber, this.page.size).subscribe(
      (res: HttpResponse<any>) => {
        if (doIncrement) {
// Your API must return paginated data - see this tutorial
          this.elements = this.elements.concat(res.body._embedded.elementDTOList);
        } else {
          this.elements = res.body._embedded.elementDTOList;
        }

        this.page = res.body.page;
        this.elements = this.elements.sort((t1, t2) => t2.date.getTime() - t1.date.getTime());
        this.elementsToShow = this.elements;
      },
      (error: HttpErrorResponse) => {
        // handle error...
      }
    );
  }

  scrollIndexChangeAction(index: number): void {
    if (this.elements[index] === undefined) return;
    this.updateMonthByIndex(this.elements[index].date.getMonth());
    // Load on down scroll event
    if (
      this.lastIndex < index && this.scrollViewPort.measureScrollOffset('bottom') < 50) {
      this.loadNextElementsBatch();
    }
    this.lastIndex = index;
  }

  monthClickAction(index: number): void {
    let compareYear = this.activeYear;
    if (index === 11) {
      compareYear--;
    }
    if (index === 0) {
      compareYear++;
    }

    if (compareYear === this.curYear && index > this.curMonth) return;

    const viewportIndex = this.elements.findIndex(el => el.date.getMonth() === index);
    if (viewportIndex === -1) {
      this.loadNextElementsBatch();
      return;
    }

    this.scrollViewPort.scrollToIndex(viewportIndex, 'smooth');
  }

  updateMonthByIndex(index: number): void {
    if (index === 11 && this.indexOfActiveMonth === 0) {
      this.activeYear--;
    }
    if (index === 0 && this.indexOfActiveMonth === 11) {
      this.activeYear++;
    }
    this.indexOfActiveMonth = index;
  }

  loadNextElementsBatch(): void {
    this.scrollViewPort.checkViewportSize();

    setTimeout(() => this.scrollViewPort.scrollTo({bottom: 0, behavior: 'smooth'}), 25);
    this.getElements(this.page.number + 1, true);
  }

  defineMoveToLeft(index: number): string {
    if (this.indexOfActiveMonth === 0 && index === this.months.length - 1) {
      return '0';
    }
    if (this.indexOfActiveMonth === this.months.length - 1 && index === 0) {
      return '66%';
    }
    if (this.indexOfActiveMonth === undefined) {
      return 33 * index + '%';
    }
    return 33 * (index - this.indexOfActiveMonth + 1) + '%';
  }

}




3. Component - scss



.element-list {

  padding-left: 15px;
  padding-right: 15px;
  height: 98%;
  width: 100%;

  .month-navigation {
    width: 100%;
    position: relative;
    height: 4.8vh;
    margin: 2vh 0;
    overflow: hidden;

    .monthView {
      display: block;
      position: absolute;
      left: 33%;
      transition: 0.2s ease-in-out left;
      top: 0;
      text-align: center;
      line-height: 3.5vh;
      font-size: 1.7vh;
      width: 33%;
      color: #aaa;

      &.active {
        color: #000;
      }
      &.active:after {
        content: '';
        display: block;
        margin: 0 auto;
        width: 50%;
        border-bottom: 0.3vh solid #000;
      }
    }
  }

  mat-form-field.mat-form-field {
    display: block;
    font-size: 2.08vh;
    line-height: 2.6vh;
  }

  .transaction-scrolling-viewport {
    height: 100%;
    width: 100%;
  }

  .load-item {
    width: 98%;

    &.bordered {
      border-bottom: 1px solid #aaa;
    }

    .content {
      width: 80%;
      margin: 1.5vh 10%;
      .text {
        color: #aaa;
        font-size: 1.6vh;
        text-align: center;
        line-height: 2.5vh;
      }
    }
  }

}




4. Model.ts for page property


  export interface HttpPage {
    size: number;
    totalElements: number;
    totalPages: number;
    number: number;
  }



5. Create inner/entry element

Create/adjust yourself this component with date,amount and mainText @input fields, or see this tutorial (coming soon)



6. Create a service method to get paginated data

  
getElementsByPageAndSize(page: number, size: number): Observable<HttpResponse<IElement[]>> {
    let params = new HttpParams();
    httpParams = httpParams .append('page', page.toString());
    httpParams = httpParams .append('size', size.toString());
    return this.http.get<IElement[]>(YOUR_SERVER_API_URL + 'api/your/elements', {
      params: httpParams ,
      observe: 'response'
    });
  }


7. IElement model

export interface IElement {
  elementName: string;
  amount: number;
  date: Date;
}