• Skip to main content
  • Skip to primary sidebar

Web Development Archive

  • Archive
You are here: Home / Archives for Angular

Angular

PrimeNG Animation

app.config.ts

import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideAnimationsAsync()]
};

Filed Under: Angular

Prefer self closing tags – eslint

ng add @angular-eslint/schematics

A telepítés után konfigurálhatod az .eslintrc fájlt a projekted gyökerében, vagy frissítheted a meglévő konfigurációs fájlt a projekted egyedi igényeinek megfelelően.

{
files: ['*.html'],
extends: ['plugin:@angular-eslint/template/recommended'],
rules: {
'@angular-eslint/template/prefer-self-closing-tags': ['error']
}
},

.eslintrc full

module.exports = {
root: true,
ignorePatterns: ['src/main.server.ts'],
overrides: [
{
files: ['*.ts', '*.js'],
plugins: ['simple-import-sort', 'unused-imports', 'sort-class-members', '@stylistic/js', 'import'],
extends: ['prettier', 'plugin:prettier/recommended', 'plugin:@angular-eslint/template/process-inline-templates'],
rules: {
'comma-dangle': ['error', 'never'],
'spaced-comment': ['error', 'always'],
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'import/no-unresolved': 'off',
'import/named': 'off',
'import/namespace': 'error',
'import/default': 'error',
'import/export': 'error',
'unused-imports/no-unused-imports': 'error',
'unused-imports/no-unused-vars': 0,
'no-duplicate-imports': 'error',
'@typescript-eslint/semi': ['error'],
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-non-null-assertion': 0,
'@typescript-eslint/no-empty-function': 'off',
'@stylistic/js/lines-around-comment': [
'warn',
{
beforeBlockComment: true,
allowBlockStart: true
}
],
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_'
}
],
'prettier/prettier': [
'error',
{
trailingComma: 'none',
endOfLine: 'auto'
}
],
'sort-class-members/sort-class-members': [
'error',
{
order: [
'[conventional-private-properties]',
'[properties]',
'[static-properties]',
'[arrow-function-properties]',
'[accessor-pairs]',
'[getters]',
'[setters]',
'[constructor]',
'[static-methods]',
'[methods]',
'[async-methods]',
'[conventional-private-methods]',
'[event-handlers]',
'[everything-else]'
],
groups: {
'event-handlers': [{ name: '/on.+/', type: 'method' }]
},
accessorPairPositioning: 'getThenSet',
stopAfterFirstProblem: true
}
],
'@typescript-eslint/lines-between-class-members': [
'error',
{ enforce: [{ blankLine: 'always', prev: '*', next: 'method' }] }
]
}
},
{
files: ['*.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['tsconfig.json'],
createDefaultProgram: true
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@angular-eslint/recommended',
'plugin:@angular-eslint/template/process-inline-templates',
'plugin:rxjs/recommended',
'plugin:prettier/recommended'
],
plugins: ['functional'],
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'hello',
style: 'camelCase'
}
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: 'hello',
style: 'kebab-case'
}
],
'@typescript-eslint/ban-types': [
'error',
{
types: {
Object: false
}
}
],
'arrow-parens': ['error', 'always'],
'no-trailing-spaces': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'functional/prefer-readonly-type': ['error', { ignoreClass: true, ignoreInterface: true }],
'@typescript-eslint/prefer-readonly': ['error'],
'rxjs/no-implicit-any-catch': 'off',
'@typescript-eslint/explicit-function-return-type': 'error',
'rxjs/finnish': [
'error',
{
names: {
store: false
}
}
],
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'memberLike',
modifiers: ['private'],
format: ['camelCase'],
leadingUnderscore: 'require'
},
{
selector: 'memberLike',
modifiers: ['public'],
format: null,
leadingUnderscore: 'forbid'
},
{
selector: 'typeLike',
format: ['PascalCase']
}
],
'@typescript-eslint/explicit-member-accessibility': [
'error',
{
accessibility: 'explicit'
}
]
}
},
{
files: ['src/app/core/backend/**/*.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'off'
}
},
{
files: ['*.html'],
extends: ['plugin:@angular-eslint/template/recommended'],
rules: {
'@angular-eslint/template/prefer-self-closing-tags': ['error']
}
},
{
files: ['*.html'],
excludedFiles: ['*inline-template-*.component.html'],
extends: ['plugin:prettier/recommended'],
rules: {
'prettier/prettier': ['error', { parser: 'angular', endOfLine: 'auto' }]
}
}
],
extends: ['plugin:storybook/recommended']
};

Filed Under: Angular Tagged With: eslint

Overlay Scrollbar

Usage

The first step is to import the CSS file into your app:

import 'overlayscrollbars/overlayscrollbars.css';

Note: If the path 'overlayscrollbars/overlayscrollbars.css' is not working use 'overlayscrollbars/styles/overlayscrollbars.css' as the import path for the CSS file.

import 'overlayscrollbars/overlayscrollbars.css';

import { OverlayscrollbarsModule } from 'overlayscrollbars-ngx';

@Component({
selector: 'app-layout',
styleUrls: ['./layout.component.scss'],
templateUrl: './layout.component.html',
standalone: true,
imports: [
OverlayscrollbarsModule,
],
})
export class LayoutComponent {}
<div class="main-container" [defer]="true" overlay-scrollbars>
<router-outlet />
</div>

Reference

Reference

Filed Under: Angular Tagged With: scroll

PrimeNg – Overlay Panel

Storybook:

decorators: [
applicationConfig({
providers: [
provideAnimations()
]
})
]

Filed Under: Angular, PrimeNG

Swiper in Angular

app/core/helpers/utils.ts

import { ElementRef } from '@angular/core';
import Swiper from 'swiper';
import { SwiperContainer } from 'swiper/element';
import { SwiperOptions } from 'swiper/types';

export function initSwiper(swiperElement: ElementRef<SwiperContainer>, options: SwiperOptions): Swiper | undefined {
if (swiperElement) {
const swiperElem = swiperElement.nativeElement;
Object.assign(swiperElem, options);
swiperElem.initialize();

return swiperElem.swiper;
}
return undefined;
}

app/app-component.ts

public constructor(@Inject(PLATFORM_ID) private readonly _platformId: InjectionToken<object>) {
if (isPlatformBrowser(this._platformId)) {
register();
}
}

swiper-component.ts

import { CommonModule } from '@angular/common';
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
CUSTOM_ELEMENTS_SCHEMA,
ElementRef,
QueryList,
ViewChildren
} from '@angular/core';
import { initSwiper } from '@app/core';

@Component({
selector: 'app-swiper',
standalone: true,
imports: [CommonModule],
templateUrl: './swiper.component.html',
styleUrls: ['./swiper.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class SwiperComponent implements AfterViewInit {
@ViewChildren('swiperContainer') public swiperContainers: QueryList<ElementRef>;

public ngAfterViewInit(): void {
this.swiperContainers.forEach((swiperContainer: ElementRef) => {
initSwiper(swiperContainer, {
slidesPerView: 1
});
});
}
}
<div class="swiper">
<swiper-container #swiperContainer>
<swiper-slide>1</swiper-slide>
<swiper-slide>2</swiper-slide>
<swiper-slide>3</swiper-slide>
<swiper-slide>4</swiper-slide>
<swiper-slide>5</swiper-slide>
<swiper-slide>6</swiper-slide>
</swiper-container>
</div>
.swiper {
max-width: 360px;
}

Filed Under: Angular

Mock Service

import { Injectable } from '@angular/core';
import { Card } from '@app/shared';

@Injectable({
providedIn: 'root'
})
export class FaqService {
public getData(): readonly Card[] {
return [
{ title: 'FAQ_1.Q', desc: 'FAQ_1.A' },
{ title: 'FAQ_2.Q', desc: 'FAQ_2.A' },
{ title: 'FAQ_3.Q', desc: 'FAQ_3.A' },
];
}
}
export class FaqComponent implements OnInit {
private readonly _faqService = inject(FaqService);
public data: readonly Card[];

public ngOnInit(): void {
this.data = this._faqService.getData();
}
}
<ng-container *ngFor="let item of data; let i = index">
<app-accordion />
</ng-container>

Filed Under: Angular

Accordion with animation

import { animate, state, style, transition, trigger } from '@angular/animations';
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';

import { IconComponent } from '../icon/icon.component';

@Component({
selector: 'app-accordion',
standalone: true,
imports: [CommonModule, IconComponent],
templateUrl: './accordion.component.html',
styleUrl: './accordion.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
trigger('slideToggle', [
state('void', style({ height: '0px', overflow: 'hidden' })),
state('active', style({ height: '*', overflow: 'hidden' })),
transition('void <=> active', animate('300ms ease-in-out'))
])
]
})
export class AccordionComponent {
@Input() public title: string;
@Input() public desc: string;
@Input() public icon: string = 'icon-chevron-down';
@Input() public isActive: boolean;

@Output() public toggled = new EventEmitter<void>();

public toggleAccordion(): void {
this.toggled.emit();
}
}
<div class="accordion">
<div class="accordion-item">
<div class="accordion-item-title" (click)="toggleAccordion()">
<div class="accordion-item-title-text" [ngClass]="{ active: isActive }">
{{ title }}
</div>
<div class="accordion-icon" [ngClass]="{ active: isActive }">
<app-icon [icon]="icon" [size]="24" />
</div>
</div>
<div class="accordion-item-panel" [@slideToggle]="isActive ? 'active' : 'void'">
<div class="accordion-item-panel-content" [innerHTML]="desc"></div>
</div>
</div>
</div>
import { Injectable } from '@angular/core';
import { Card } from '@app/shared';

@Injectable({
providedIn: 'root'
})
export class FaqService {
public getData(): readonly Card[] {
return [
{ title: 'FAQ_1.Q', desc: 'FAQ_1.A' },
{ title: 'FAQ_2.Q', desc: 'FAQ_2.A' },
{ title: 'FAQ_3.Q', desc: 'FAQ_3.A' },
];
}
}
import { Link } from './link.model';

export type Card = Readonly<{
readonly title: string;
readonly desc?: string;
readonly cta?: Link;
}>;
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core';
import { AccordionComponent, Card } from '@app/shared';
import { TranslocoPipe } from '@jsverse/transloco';

import { FaqService } from '../../shared/services/faq.service';

@Component({
selector: 'app-faq',
standalone: true,
imports: [CommonModule, TranslocoPipe, AccordionComponent],
templateUrl: './faq.component.html',
styleUrl: './faq.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FaqComponent implements OnInit {
private readonly _faqService = inject(FaqService);
public activeAccordionIndex: number | null = null;
public data: readonly Card[];

public ngOnInit(): void {
this.data = this._faqService.getData();
}

public onAccordionToggle(index: number): void {
if (this.activeAccordionIndex === index) {
this.activeAccordionIndex = null;
} else {
this.activeAccordionIndex = index;
}
}
}
<section class="faq">
<div class="container">
<div class="heading">
<h2 class="heading-2">{{ 'FAQ.TITLE' | transloco }}</h2>
<p class="subtitle">{{ 'FAQ.SUBTITLE' | transloco }}</p>
</div>
<div class="accordion">
<ng-container *ngFor="let item of data; let i = index">
<app-accordion
[title]="item.title | transloco"
[desc]="item.desc | transloco"
[isActive]="activeAccordionIndex === i"
(toggled)="onAccordionToggle(i)"
/>
</ng-container>
</div>
</div>
</section>
@use 'shared' as *;

:host {
display: block;
}

.accordion {
width: 100%;

.grid-2 {
gap: 80px;
}
.title.desktop {
display: none;
@include media-breakpoint-up(md) {
display: block;
}
}
.title.mobile {
display: block;
margin-bottom: -40px;
@include media-breakpoint-up(md) {
display: none;
}
}
&-caption {
order: 2;
@include media-breakpoint-up(md) {
order: 1;
}
.title {
margin-bottom: 80px;
}
}
}

.accordion {
&-icon {
width: 24px;
height: 24px;
transition: transform 0.3s ease-in-out;
&.active {
transform: rotate(180deg);
}
}
&-item {
background-color: var(--color-white);
border: 1px solid var(--color-grey-300);
border-radius: 8px;
margin-bottom: 16px;

&-title {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
padding: 16px;
&-text {
color: var(--color-primary-950);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 120%;
max-width: 94%;
}
}
&-panel {
overflow: hidden;
color: var(--color-primary-950);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 120%;
&-content {
padding: 0 16px 16px;
}

&.active {
border-top: 1px solid var(--color-grey-300);
}
}
}
}

main.ts

import { bootstrapApplication } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';

import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';

bootstrapApplication(AppComponent, {
...appConfig,
providers: [provideAnimations(), ...(appConfig.providers || [])]
}).catch((err) => console.error(err));

Filed Under: Angular

Modal / Dialog

<button (click)="onClickOrderDialog($event)" />
public onClickOrderDialog(event: Event): void {
event.preventDefault();
const dialogRef = this._dialogService.open(DialogComponent);
dialogRef.afterClosed$().subscribe(() => {});
this._cdr.markForCheck();
}
export class OrderingDialogComponent implements OnInit, OnDestroy {
private readonly _deviceService = inject(DeviceService);
private readonly _cdr = inject(ChangeDetectorRef);
private readonly _destroy$ = new Subject<void>();
public isMobile: boolean = false;
public data: string;
public FieldType = FieldType;

public constructor(
@Inject(DIALOG_DATA) dialogData: string,
private readonly _dialogRef: DialogRef<unknown>
) {
this.data = dialogData;
}

public ngOnInit(): void {
this._deviceService.isMobileObs$.pipe(takeUntil(this._destroy$)).subscribe((isMobile) => {
this.isMobile = isMobile;
this._cdr.markForCheck();
});
}

public ngOnDestroy(): void {
this._destroy$.next();
this._destroy$.complete();
}

public onClose(): void {
if (this._dialogRef) {
this._dialogRef.close(null);
}
}
}

Filed Under: Angular

Dynamic Dropdown Component with Close by Click Outside

export class DropdownComponent {
@Input() public isButton: boolean = false;
@Input() public isIconOnly: boolean = false;
@Input() public icon: string = 'icon-dots-vertical';
@Input() public appendTo: string | ElementRef | null = null;
@ViewChild('dropdownMenu') public dropdownMenu!: ElementRef;
@ViewChild(IconComponent, { read: ElementRef }) public iconElement!: ElementRef;

public ButtonGroup = ButtonGroup;
public ButtonIcon = ButtonIcon;
public ButtonType = ButtonType;

public isDropdownOpen = false;

public constructor(
private readonly _renderer: Renderer2,
private readonly _hostElement: ElementRef
) {}

public toggleDropdown(): void {
this.isDropdownOpen = !this.isDropdownOpen;

if (this.isDropdownOpen && this.appendTo) {
setTimeout(() => {
this.appendDropdownToTarget();
});
} else if (!this.isDropdownOpen) {
this.resetDropdownPosition();
}
}

public appendDropdownToTarget(): void {
const dropdownMenuElement = this.dropdownMenu.nativeElement;
let targetElement: HTMLElement | null = null;

if (this.appendTo === 'body') {
targetElement = document.body;
} else if (this.appendTo instanceof ElementRef) {
targetElement = this.appendTo.nativeElement;
} else if (typeof this.appendTo === 'string') {
targetElement = document.querySelector(this.appendTo);
}

if (targetElement) {
this._renderer.appendChild(targetElement, dropdownMenuElement);

const iconRect = this.iconElement.nativeElement.getBoundingClientRect();

const top = iconRect.bottom + window.scrollY;
const left = iconRect.left + window.scrollX;

this._renderer.setStyle(dropdownMenuElement, 'position', 'absolute');
this._renderer.setStyle(dropdownMenuElement, 'top', `${top}px`);
this._renderer.setStyle(dropdownMenuElement, 'left', `${left}px`);
this._renderer.setStyle(dropdownMenuElement, 'min-width', `${iconRect.width}px`);
}
}

public resetDropdownPosition(): void {
const dropdownMenuElement = this.dropdownMenu.nativeElement;
this._renderer.appendChild(this._hostElement.nativeElement, dropdownMenuElement);
}

@HostListener('document:click', ['$event.target'])
public onClickOutside(targetElement: HTMLElement): void {
const clickedInsideHost = this._hostElement.nativeElement.contains(targetElement);
const clickedInsideDropdown = this.dropdownMenu?.nativeElement.contains(targetElement);

if (!clickedInsideHost && !clickedInsideDropdown) {
this.isDropdownOpen = false;
}
}
}
<div class="dropdown-container">
<app-button
[label]="'SHARED.RUNNING_ADS' | transloco"
[buttonType]="ButtonType.SECONDARY"
[buttonGroup]="ButtonGroup.TEXT"
[buttonIcon]="ButtonIcon.ICON_RIGHT"
[icon]="'icon-chevron-down-2'"
[isWide]="true"
[isDisabled]="false"
[isDarkmode]="true"
(click)="toggleDropdown()"
/>
@if (isDropdownOpen) {
<div class="dropdown-menu">
<ng-content />
</div>
}
</div>
@use 'shared' as *;

.dropdown-container {
position: relative;
display: inline-block;
cursor: pointer;
}

.dropdown-menu {
border-radius: 8px;
box-shadow: 0px 16px 16px 0px rgba(217, 218, 243, 0.3);
background: var(--color-white);
border: 1px solid var(--color-grey-300);
position: absolute;
right: 0;
width: 360px;
z-index: 1000;
overflow: auto;
}

.dropdown-menu.show {
display: block;
}

Filed Under: Angular

Dropdown Desktop / Dialog Mobile

@if (!isMobile) {
<app-dropdown [appendTo]="'body'">
<app-running-ads-dropdown />
</app-dropdown>
} @else {
<app-button
[label]="'SHARED.RUNNING_ADS' | transloco"
[buttonType]="ButtonType.SECONDARY"
[buttonGroup]="ButtonGroup.TEXT"
[buttonIcon]="ButtonIcon.ICON_RIGHT"
[icon]="'icon-chevron-down-2'"
[isWide]="true"
[isDisabled]="false"
[isDarkmode]="true"
(click)="onClickRunningAds($event)"
/>
}
public onClickRunningAds(event: Event): void {
event.preventDefault();
const dialogRef = this._dialogService.open(RunningAdsDialogComponent);
dialogRef.afterClosed$().subscribe(() => {});
this._cdr.markForCheck();
}
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { CheckboxComponent } from '@app/shared/components/checkbox/checkbox.component';

@Component({
selector: 'app-running-ads-dropdown',
standalone: true,
imports: [CommonModule, CheckboxComponent],
templateUrl: './running-ads-dropdown.component.html',
styleUrl: './running-ads-dropdown.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RunningAdsDropdownComponent {
public activeIndex: number | null = null;
public checkboxArchivedAds: string[] = ['Szűrés az archivált hirdetésekre is '];

public checkboxRunningAds: string[] = [
'Futó hirdetések',
'Elszámolás alatt levő hirdetések',
'Leállított hirdetések',
'Befejeződött hirdetések'
];

public selectedTitles: { [key: string]: boolean } = {
'Szűrés az archivált hirdetésekre is': true
};

public setActiveIndex(index: number): void {
this.activeIndex = index;
}

public isChecked(title: string): boolean {
return !!this.selectedTitles[title];
}

public generateId(index: number): string {
return `checkbox-${index}`;
}

public onCheckboxChange(title: string): void {
this.selectedTitles[title] = !this.selectedTitles[title];
}
}
<div class="running-ads-dropdown">
<app-checkbox
*ngFor="let item of checkboxArchivedAds; let i = index"
[id]="'archived-ads'"
[isBorder]="true"
[htmlTitle]="item"
[isChecked]="isChecked(item)"
[ngClass]="{ checked: isChecked(item) }"
(checkedChange)="onCheckboxChange(item)"
/>
<div class="divider"></div>
<app-checkbox
*ngFor="let item of checkboxRunningAds; let i = index"
[id]="generateId(i)"
[isBorder]="true"
[htmlTitle]="item"
[isChecked]="isChecked(item)"
[ngClass]="{ checked: isChecked(item) }"
(checkedChange)="onCheckboxChange(item)"
/>
</div>
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Inject,
inject,
OnDestroy,
OnInit
} from '@angular/core';
import { DeviceService, DIALOG_DATA, DialogRef } from '@app/core/services';
import { FieldType } from '@app/shared/components/field/field.enum';
import { ModalComponent } from '@app/shared/components/modal/modal.component';
import { TranslocoPipe } from '@jsverse/transloco';
import { Subject, takeUntil } from 'rxjs';

import { RunningAdsDropdownComponent } from '../running-ads-dropdown/running-ads-dropdown.component';

@Component({
selector: 'app-running-ads-dialog',
standalone: true,
imports: [CommonModule, ModalComponent, RunningAdsDropdownComponent, TranslocoPipe],
templateUrl: './running-ads-dialog.component.html',
styleUrl: './running-ads-dialog.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RunningAdsDialogComponent implements OnInit, OnDestroy {
private readonly _deviceService = inject(DeviceService);
private readonly _cdr = inject(ChangeDetectorRef);
private readonly _destroy$ = new Subject<void>();
public isMobile: boolean = false;
public data: string;
public FieldType = FieldType;

public constructor(
@Inject(DIALOG_DATA) dialogData: string,
private readonly _dialogRef: DialogRef<unknown>
) {
this.data = dialogData;
}

public ngOnInit(): void {
this._deviceService.isMobileObs$.pipe(takeUntil(this._destroy$)).subscribe((isMobile) => {
this.isMobile = isMobile;
this._cdr.markForCheck();
});
}

public ngOnDestroy(): void {
this._destroy$.next();
this._destroy$.complete();
}

public onClose(): void {
if (this._dialogRef) {
this._dialogRef.close(null);
}
}
}
<app-modal
[isVisible]="true"
[header]="'ADS.STATS_FILTER' | transloco"
[btnPrimaryLabel]="'ADS.FILTER' | transloco"
[btnSecondaryLabel]="'SHARED.CANCEL' | transloco"
[isMobile]="isMobile"
[isBtnPrimaryVisible]="true"
[isBtnSecondaryVisible]="true"
(btnPrimaryClick)="onClose()"
(closeClick)="onClose()"
>
<div body>
<div class="dialog-content">
<app-running-ads-dropdown />
</div>
</div>
</app-modal>

Filed Under: Angular

  • « Go to Previous Page
  • Page 1
  • Page 2
  • Page 3
  • Page 4
  • Page 5
  • Page 6
  • Go to Next Page »

Primary Sidebar

  • angular.io
© 2026 WP Flames - All Right Reserved