• Skip to main content
  • Skip to primary sidebar

Web Development Archive

  • Archive
You are here: Home / Archives for Other

Other

SVG Icons in Angular

import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';

@Component({
selector: 'app-icon',
standalone: true,
imports: [CommonModule],
templateUrl: './icon.component.html',
styleUrl: './icon.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class IconComponent {
@Input() public icon: string | undefined;
@Input() public size: number | undefined;
@Input() public rotate: number = 0;
}
<svg [attr.height]="size" [attr.width]="size" [style.transform]="rotate ? 'rotate(' + rotate + 'deg)' : ''">
<use [attr.href]="'assets/images/icons/svg-icon-sprite.svg#' + icon" />
</svg>
:host {
display: flex;

use {
display: block;
height: 100%;
width: 100%;
fill: currentColor;
}

svg {
transition: transform 0.3s ease;
margin: auto;
}
}
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="icon-save" viewBox="0 0 20 20" fill="none">
<path d="M5.83333 2.5V5.33333C5.83333 5.80004 5.83333 6.0334 5.92416 6.21166C6.00406 6.36846 6.13154 6.49594 6.28834 6.57584C6.4666 6.66667 6.69996 6.66667 7.16667 6.66667H12.8333C13.3 6.66667 13.5334 6.66667 13.7117 6.57584C13.8685 6.49594 13.9959 6.36846 14.0758 6.21166C14.1667 6.0334 14.1667 5.80004 14.1667 5.33333V3.33333M14.1667 17.5V12.1667C14.1667 11.7 14.1667 11.4666 14.0758 11.2883C13.9959 11.1315 13.8685 11.0041 13.7117 10.9242C13.5334 10.8333 13.3 10.8333 12.8333 10.8333H7.16667C6.69996 10.8333 6.4666 10.8333 6.28834 10.9242C6.13154 11.0041 6.00406 11.1315 5.92416 11.2883C5.83333 11.4666 5.83333 11.7 5.83333 12.1667V17.5M17.5 7.77124V13.5C17.5 14.9001 17.5 15.6002 17.2275 16.135C16.9878 16.6054 16.6054 16.9878 16.135 17.2275C15.6002 17.5 14.9001 17.5 13.5 17.5H6.5C5.09987 17.5 4.3998 17.5 3.86502 17.2275C3.39462 16.9878 3.01217 16.6054 2.77248 16.135C2.5 15.6002 2.5 14.9001 2.5 13.5V6.5C2.5 5.09987 2.5 4.3998 2.77248 3.86502C3.01217 3.39462 3.39462 3.01217 3.86502 2.77248C4.3998 2.5 5.09987 2.5 6.5 2.5H12.2288C12.6364 2.5 12.8402 2.5 13.0321 2.54605C13.2021 2.58688 13.3647 2.65422 13.5138 2.7456C13.682 2.84867 13.8261 2.9928 14.1144 3.28105L16.719 5.88562C17.0072 6.17387 17.1513 6.318 17.2544 6.48619C17.3458 6.63531 17.4131 6.79789 17.4539 6.96795C17.5 7.15976 17.5 7.36358 17.5 7.77124Z"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
</defs>
</svg>
<app-icon [icon]='icon-save' [size]='20' />

Filed Under: Other

Local Google Font in Angular

Google Webfonts Helper

assets/fonts/inter/…

scss/base/_typography.scss

@use 'shared' as *;

* {
font-family: var(--font-family);
}

scss/base/_fonts.scss

@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 100;
src: url('../../assets/fonts/inter/Inter-Thin.ttf') format('truetype');
}

@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 200;
src: url('../../assets/fonts/inter/Inter-ExtraLight.ttf') format('truetype');
}

@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
src: url('../../assets/fonts/inter/Inter-Light.ttf') format('truetype');
}

@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
src: url('../../assets/fonts/inter/Inter-Regular.ttf') format('truetype');
}

@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
src: url('../../assets/fonts/inter/Inter-Medium.ttf') format('truetype');
}

@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
src: url('../../assets/fonts/inter/Inter-SemiBold.ttf') format('truetype');
}

@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
src: url('../../assets/fonts/inter/Inter-Bold.ttf') format('truetype');
}

@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
src: url('../../assets/fonts/inter/Inter-ExtraBold.ttf') format('truetype');
}

@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 900;
src: url('../../assets/fonts/inter/Inter-Black.ttf') format('truetype');
}

scss/abstracts/_var-exports.scss

$font-family: 'Inter', sans-serif;@use './variables' as var;

scss/abstracts/_variables.scss

@use './variables' as var;

@mixin export-vars() {
--font-family: #{var.$font-family};
}

Filed Under: Other

Footer Example

<footer class="footer">
<div class="footer-container">
<div class="footer-logo">
<app-logo />
</div>
<ul class="footer-nav">
<li class="footer-nav-list">
<a class="footer-nav-link" href="">{{ 'FOOTER.TERMS' | transloco }}</a>
</li>
<li class="footer-nav-list">
<a class="footer-nav-link" href="">{{ 'FOOTER.PRIVACY' | transloco }}</a>
</li>
<li class="footer-nav-list">
<a class="footer-nav-link" href="">{{ 'FOOTER.IMPRESSUM' | transloco }}</a>
</li>
</ul>
</div>
</footer>
@use 'shared' as *;

:host {
display: block;
}

.footer{
background: var(--color-primary-950);
margin-top: 48px;
padding: 32px 24px;
@include media-breakpoint-up(lg){
padding: 48px 24px;
}
&-container{
max-width: 1110px;
width: 100%;
margin: auto;
@include media-breakpoint-up(lg){
display: flex;
justify-content: flex-start;
gap: 62px;
}
}
&-logo{
margin-bottom: 16px;
@include media-breakpoint-up(lg){
margin: 0;
}
}
&-nav{
display: flex;
flex-direction: column;
gap: 16px;
@include media-breakpoint-up(lg){
align-items: center;
flex-direction: row
}
&-list{
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 120%;
}
&-link{
color: var(--color-grey-400);
transition: .3s;
display: block;
&:hover{
color: var(--color-white);
}
}
}
}

Filed Under: Other

Header Example with Hamburger Menu

import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { LogoComponent } from '@app/shared';
import { AuthButtonsComponent } from '@app/shared/components/auth-buttons/auth-buttons.component';
import { ButtonGroup, ButtonIcon, ButtonType } from '@app/shared/components/button';
import { TranslocoPipe } from '@jsverse/transloco';

@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
standalone: true,
imports: [CommonModule, AuthButtonsComponent, TranslocoPipe, LogoComponent],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderComponent {
public ButtonGroup = ButtonGroup;
public ButtonIcon = ButtonIcon;
public ButtonType = ButtonType;

public isActive = false;

public toggleMenu(): void {
this.isActive = !this.isActive;
}

public closeMenu(): void {
this.isActive = false;
}
}
<header class="header">
<div class="wrapper header-wrapper">
<figure class="header-logo">
<a class="header-logo-link" [href]="">
<app-logo />
</a>
</figure>
<nav class="header-nav">
<div class="header-nav-toggle" [class.active]="isActive" (click)="toggleMenu()">
<span></span>
<span></span>
<span></span>
</div>
<div class="header-nav-list-wrapper" [class.active]="isActive">
<ul class="header-nav-list">
<li class="header-nav-list-item">
<a class="header-nav-list-item-link" href="">{{ 'HEADER_MENU.HOW_IT_WORKS' | transloco }}</a>
</li>
<li class="header-nav-list-item">
<a class="header-nav-list-item-link" href="">{{ 'HEADER_MENU.PRICES' | transloco }}</a>
</li>
<li class="header-nav-list-item">
<a class="header-nav-list-item-link" href="">{{ 'HEADER_MENU.NEWS' | transloco }}</a>
</li>
<li class="header-nav-list-item">
<a class="header-nav-list-item-link" href="">{{ 'HEADER_MENU.FAQ' | transloco }}</a>
</li>
<li class="header-nav-list-item">
<a class="header-nav-list-item-link" href="">{{ 'HEADER_MENU.CONTACT' | transloco }}</a>
</li>
</ul>
<div class="header-buttons-mobile">
<app-auth-buttons />
</div>
</div>
</nav>
<div class="header-buttons">
<app-auth-buttons />
</div>
</div>
</header>
@use 'shared' as *;

:host {
display: block;
}

:host {
background: var(--color-primary-950);
border-radius: 0px 0px 16px 16px;
color: var(--color-white);
display: block;
margin: auto;
padding: 20px;
font-family: $font-family;
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 24px;
width: 100%;
max-width: 1110px;
}

.header {
display: flex;
justify-content: center;
&-buttons {
display: none;
@include media-breakpoint-up(lg) {
display: flex;
gap: 12px;
justify-content: flex-end;
}
&-mobile{
display: flex;
flex-direction: column;
gap: 30px;
justify-content: flex-end;
margin: 30px auto;
padding: 0 30px;
max-width: 300px;
@include media-breakpoint-up(lg){
display: none;
}
}
}
&-wrapper {
display: flex;
justify-content: space-between;
}
&-logo {
display: flex;
align-items: center;
z-index: 9;
}
&-nav {
display: flex;
align-items: center;
position: relative;
&-toggle {
transition: 0.8s;
z-index: 9;
width: 36px;
height: 28px;
@include media-breakpoint-up(lg) {
display: none;
}
span {
position: relative;
width: 28px;
height: 2px;
background: var(--color-purple-300);
border-radius: 2px;
display: block;
margin-bottom: 8px;
transition: 0.5s;

&:nth-child(1) {
transform-origin: left;
}
&:nth-child(2) {
transform-origin: center;
}
&:nth-child(3) {
transform-origin: left;
margin-bottom: 0;
}
}
&.active {
transition: 0.5s;
z-index: 9;
width: 36px;
height: 28px;
span {
background: var(--color-purple-300);
}
span:nth-child(1) {
transform: rotate(45deg);
left: 2px;
}
span:nth-child(2) {
transform: rotate(315deg);
right: 3px;
}
span:nth-child(3) {
opacity: 0;
transform: scale(0);
}
}
}
&-list {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 80px;
@include media-breakpoint-up(lg){
margin-top: 0;
}
&-wrapper{
background: var(--color-primary-950);
transition: 0.5s;
position: fixed;
top: 0;
width: 100%;
height: 100%;
z-index: 2;
right: -100%;
@include media-breakpoint-up(lg){
position: static;
display: flex;
}
&.active {
background: $black-opacity;
right: 0;
}
}
@include media-breakpoint-up(lg) {
display: flex;
justify-content: flex-end;
align-items: center;
position: static;
flex-direction: row;
width: auto;
}
&-item {
display: block;
width: 100%;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 120%;
@include media-breakpoint-up(lg) {
width: auto;
}
&:first-child {
.header-nav-list-item-link {
border-top: 1px solid $border;
@include media-breakpoint-up(lg) {
border: none;
}
}
}
&-link {
display: block;
width: 100%;
border-bottom: 1px solid $border;
text-align: center;
color: $white;
transition: 0.4s;
padding: 30px;
@include media-breakpoint-up(lg) {
border: none;
width: auto;
padding: 0 20px;
}
&:hover {
color: var(--color-purple-200);
}
}
}
}
}
}
.cta {
display: none;
@include media-breakpoint-up(lg) {
display: block;
}
&.mobile {
display: block;
margin-top: 30px;
text-align: center;
max-width: 200px;
margin: 30px auto;
@include media-breakpoint-up(lg) {
display: none;
}
}
}

Filed Under: Other

Inject Service

private readonly _mockService = inject(MockService);

Filed Under: Other

Card Toggle Menu

<div class="card ad-card" [ngClass]="data.isMobile ? 'mobile' : 'desktop'" (click)="closeMenu()">
<div class="card-header">
<div class="card-header-title">{{ data.cardTitle }}</div>
<div class="card-header-icon">
<app-card-button (click)="menuToggle($event)" />
@if (isMenuOpened) {
<ng-container *ngTemplateOutlet="menu" />
}
</div>
</div>
<div class="card-body">
<div class="stats-wrapper">
<app-stats [type]="StatType.CLICKS" [statLabel]="'Kattintások'" [statValue]="50" />
<app-stats [type]="StatType.BUDGET" [statLabel]="'Keret'" [statValue]="30000" />
<app-stats [type]="StatType.CTR" [statLabel]="'CTR'" [statValue]="0.023" />
<app-stats [type]="StatType.CPC" [statLabel]="'CPC'" [statValue]="133" />
</div>
<div class="bar-wrapper">
<app-progress-bar-2
[type]="BarType.APPEARANCE"
[currentNum]="data.currentNum ?? 0"
[allNum]="data.allNum ?? 0"
[isLabelRight]="true"
/>
<app-progress-bar-2
[type]="BarType.BUDGET"
[currentMoney]="data.currentMoney ?? 0"
[allMoney]="data.allMoney ?? 0"
[isLabelRight]="true"
/>
</div>
</div>
<div class="card-footer">
<div class="card-footer-wrapper">
<app-status [icon]="'icon-play-circle'" [text]="'Fut'" [statusType]="StatusType.RUNNING" />
<div class="card-footer-label">
<span class="card-footer-label-text">{{ data.footerLabelText }}</span>
<span class="card-footer-label-date">{{ data.footerLabelDate | date: 'yyyy.MM.dd. HH:mm' }}</span>
</div>
</div>
<div class="card-footer-warning" *ngIf="data.isLowerThanExpected">{{ data.lowerThanExpectedText }}</div>
</div>
</div>
<ng-template #menu>
<div class="menu" [ngClass]="isMobile ? 'mobile' : 'desktop'">
<button class="menu-item" (click)="onView()">{{ 'ADS.VIEW' | transloco }}</button>
<button class="menu-item" (click)="onChange()">{{ 'ADS.CHANGE' | transloco }}</button>
<button class="menu-item" (click)="onRestart()">{{ 'ADS.RESTART' | transloco }}</button>
<span class="menu-item-divider"></span>
<button class="menu-item" (click)="onDelete()">{{ 'ADS.DELETE' | transloco }}</button>
</div>
</ng-template>
.menu {
position: absolute;
right: 0;
border-radius: 8px;
box-shadow: 0px 16px 16px 0px rgba(217, 218, 243, 0.3);
background: var(--color-white);
&.desktop {
border: 1px solid var(--color-grey-300);
position: absolute;
top: 20px;
width: 200px;
z-index: 9;
.menu-item {
padding: 12px 30px;
}
}
&-item {
padding: 12px 0;
color: var(--color-primary-950);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 120%;
display: block;
width: 100%;
text-align: left;
&:hover {
background: var(--color-grey-100);
}
&-divider {
background: var(--color-grey-300);
display: block;
width: 100%;
height: 1px;
}
}
}
export class AdCardComponent implements OnInit, OnDestroy {
private readonly _eref = inject(ElementRef);
private readonly _deviceService = inject(DeviceService);
private readonly _cdr = inject(ChangeDetectorRef);
private readonly _destroy$ = new Subject();

@Input() public data: AdCard;
@Input() public isMobile: boolean = false;
public isMenuOpened: boolean = false;

public StatusType = StatusType;
public StatType = StatType;
public BarType = BarType;

@Output() public viewEvent = new EventEmitter<CardMember>();
@Output() public changeEvent = new EventEmitter<CardMember>();
@Output() public duplicateEvent = new EventEmitter<CardMember>();
@Output() public archiveEvent = new EventEmitter<CardMember>();
@Output() public startEvent = new EventEmitter<CardMember>();
@Output() public stopEvent = new EventEmitter<CardMember>();
@Output() public restartEvent = new EventEmitter<CardMember>();
@Output() public deleteEvent = new EventEmitter<CardMember>();

public get classes(): string[] {
const deviceType = this.isMobile ? 'mobile' : 'desktop';
return [deviceType];
}

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

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

@HostListener('document:click', ['$event.target'])
public clickOutside(targetElement: HTMLElement): void {
const clickedInside = this._eref.nativeElement.contains(targetElement);
if (!clickedInside) {
this.isMenuOpened = false;
}
}

public menuToggle($event: MouseEvent): void {
$event.stopPropagation();
this.isMenuOpened = !this.isMenuOpened;
}

public closeMenu(): void {
this.isMenuOpened = false;
}

public onView(): void {
this.viewEvent.emit();
}

public onChange(): void {
this.changeEvent.emit();
}

public onStart(): void {
this.startEvent.emit();
}

public onDelete(): void {
this.deleteEvent.emit();
}
}

Filed Under: Other

Close menu by clickOutside

private readonly _eref = inject(ElementRef);

@HostListener('document:click', ['$event.target'])
public clickOutside(targetElement: HTMLElement): void {
const clickedInside = this._eref.nativeElement.contains(targetElement);
if (!clickedInside) {
this.isMenuOpened = false;
}
}

Filed Under: Other

Dialog Service

import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal, ComponentType } from '@angular/cdk/portal';
import { inject, Injectable, InjectionToken, Injector } from '@angular/core';
import { Observable, Subject } from 'rxjs';

export const DIALOG_DATA = new InjectionToken<unknown>('DIALOG_DATA');

export class DialogRef<T> {
private readonly _afterClosed$ = new Subject<T | null>();

public constructor(private readonly _overlayRef: OverlayRef) {}

public close(result: T | null): void {
this._overlayRef.dispose();
this._afterClosed$.next(result);
this._afterClosed$.complete();
}

public afterClosed$(): Observable<T | null> {
return this._afterClosed$.asObservable();
}
}

@Injectable({
providedIn: 'root',
})
export class DialogService {
private readonly _overlay = inject(Overlay);
private readonly _rootInjector = inject(Injector);
private readonly _overlayRef: OverlayRef;

public open<T, D>(component: ComponentType<T>, data?: D): DialogRef<D | null> {
const overlayConfig = {
hasBackdrop: true,
backdropClass: 'cdk-overlay-dark-backdrop',
panelClass: 'modal-overlay-panel',
scrollStrategy: this._overlay.scrollStrategies.block(),
positionStrategy: this._overlay.position().global().centerHorizontally().centerVertically(),
};

const overlayRef = this._overlay.create(overlayConfig);
const dialogRef: DialogRef<D | null> = new DialogRef<D | null>(overlayRef);

const injector = this._createInjector(data, dialogRef);
const componentPortal = new ComponentPortal(component, null, injector);
overlayRef.attach(componentPortal);

overlayRef.backdropClick().subscribe(() => {
dialogRef.close(null);
});

return dialogRef;
}

public close(): void {
if (this._overlayRef) {
this._overlayRef.dispose();
}
}

private _createInjector<D>(data: D | null, dialogRef: unknown): Injector {
return Injector.create({
providers: [
{ provide: DIALOG_DATA, useValue: data },
{ provide: DialogRef, useValue: dialogRef },
],
parent: this._rootInjector,
});
}
}

Modal

import { animate, state, style, transition, trigger } from '@angular/animations';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
inject,
Input,
OnDestroy,
OnInit,
Output,
} from '@angular/core';
import { DeviceService } from '@app/core/services';
import { ButtonComponent } from '@app/shared/components/button/button.component';
import { ButtonGroup, ButtonIcon, ButtonType } from '@app/shared/components/button/button.enum';
import { IconComponent } from '@app/shared/components/icon/icon.component';
import { TranslocoPipe } from '@jsverse/transloco';
import { Subject, takeUntil } from 'rxjs';

@Component({
selector: 'app-modal',
standalone: true,
imports: [CommonModule, ButtonComponent, IconComponent, TranslocoPipe],
templateUrl: './modal.component.html',
styleUrl: './modal.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
trigger('dialog', [
state('void', style({ opacity: 0, transform: 'scale(0.9)' })),
state('enter', style({ opacity: 1, transform: 'scale(1)' })),
transition('void => enter', [animate('100ms ease-out')]),
transition('enter => void', [animate('100ms ease-in')]),
]),
],
})
export class ModalComponent implements OnInit, OnDestroy {
private readonly _deviceService = inject(DeviceService);
private readonly _cdr = inject(ChangeDetectorRef);
private readonly _destroy$ = new Subject();
@Input() public isVisible: boolean = false;
@Input() public isMobile: boolean;
@Input() public isHeaderCloseVisible: boolean = true;
@Input() public header: string;
@Input() public btnPrimaryLabel?: string;
@Input() public btnSecondaryLabel?: string;
@Input() public isBtnPrimaryVisible?: boolean;
@Input() public isBtnSecondaryVisible?: boolean;
@Output() public isVisibleChange = new EventEmitter<boolean>();
@Output() public closeClick = new EventEmitter<void>();

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

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

public get classes(): string[] {
const deviceType = this.isMobile ? 'mobile' : 'desktop';
return [deviceType];
}

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

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

public onClose(): void {
this.closeClick.emit();
this.isVisible = false;
}

public onBtnPrimary(): void {
this.btnPrimaryClick.emit();
}

public onBtnSecondary(): void {
this.btnSecondaryClick.emit();
}

public onOverlayClick(event: Event): void {
if ((event.target as HTMLElement).classList.contains('modal-overlay')) {
this.isVisibleChange.emit(false);
}
}
}
<div class="modal" [ngClass]="classes">
<div class="modal-header">
<h4 class="modal-header-title">{{ header }}</h4>
@if (isHeaderCloseVisible) {
<span class="modal-header-icon">
<app-icon [icon]="'icon-close'" [size]="24" (click)="onClose()" />
</span>
}
</div>
<div class="modal-body">
<ng-content select="[body]" />
</div>
<div
class="modal-footer"
*ngIf="isBtnPrimaryVisible || isBtnSecondaryVisible"
[ngClass]="{ flexend: !isBtnSecondaryVisible }"
>
<div class="modal-footer-button">
@if (isBtnPrimaryVisible) {
<app-button
class="btn-primary"
[label]="btnPrimaryLabel"
[buttonType]="ButtonType.PRIMARY"
[buttonGroup]="ButtonGroup.DEFAULT"
[buttonIcon]="ButtonIcon.DEFAULT"
[isWide]="true"
[isDisabled]="false"
[isDarkmode]="false"
(buttonClick)="onBtnPrimary()"
/>
}
</div>
<div class="modal-footer-button">
@if (isBtnSecondaryVisible) {
<app-button
class="btn-secondary"
*ngIf="btnSecondaryLabel"
[label]="btnSecondaryLabel"
[buttonType]="ButtonType.PRIMARY"
[buttonGroup]="ButtonGroup.OUTLINED"
[buttonIcon]="ButtonIcon.DEFAULT"
[isWide]="true"
[isDisabled]="false"
[isDarkmode]="false"
(buttonClick)="onBtnSecondary()"
/>
}
</div>
</div>
</div>

Use Dialog Service

<div class="modal" [ngClass]="classes">
<div class="modal-header">
<h4 class="modal-header-title">{{ header }}</h4>
@if (isHeaderCloseVisible) {
<span class="modal-header-icon">
<app-icon [icon]="'icon-close'" [size]="24" (click)="onClose()" />
</span>
}
</div>
<div class="modal-body">
<ng-content select="[body]" />
</div>
<div
class="modal-footer"
*ngIf="isBtnPrimaryVisible || isBtnSecondaryVisible"
[ngClass]="{ flexend: !isBtnSecondaryVisible }"
>
<div class="modal-footer-button">
@if (isBtnPrimaryVisible) {
<app-button
class="btn-primary"
[label]="btnPrimaryLabel"
[buttonType]="ButtonType.PRIMARY"
[buttonGroup]="ButtonGroup.DEFAULT"
[buttonIcon]="ButtonIcon.DEFAULT"
[isWide]="true"
[isDisabled]="false"
[isDarkmode]="false"
(buttonClick)="onBtnPrimary()"
/>
}
</div>
<div class="modal-footer-button">
@if (isBtnSecondaryVisible) {
<app-button
class="btn-secondary"
*ngIf="btnSecondaryLabel"
[label]="btnSecondaryLabel"
[buttonType]="ButtonType.PRIMARY"
[buttonGroup]="ButtonGroup.OUTLINED"
[buttonIcon]="ButtonIcon.DEFAULT"
[isWide]="true"
[isDisabled]="false"
[isDarkmode]="false"
(buttonClick)="onBtnSecondary()"
/>
}
</div>
</div>
</div>
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Inject,
inject,
OnDestroy,
OnInit,
} from '@angular/core';
import { DeviceService } from '@app/core/services';
import { DIALOG_DATA, DialogRef } from '@app/core/services/dialog.service';
import { FieldComponent } from '@app/shared/components/field/field.component';
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 { StatsInfoDialogContentComponent } from '../stats-info-dialog-content/stats-info-dialog-content.component';

@Component({
selector: 'app-stats-info-dialog',
standalone: true,
imports: [ModalComponent, FieldComponent, TranslocoPipe, StatsInfoDialogContentComponent],
templateUrl: './stats-info-dialog.component.html',
styleUrl: './stats-info-dialog.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StatsInfoDialogComponent 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: Other

Add active class only to the selected card

Child Component

export class ChildComponent {
@Input() public isActive: boolean = false;
@Output() public statClick = new EventEmitter<Event>();

public get classes(): string[] {
const active = this.isActive ? 'isActive' : '';
return [active];
}

public onStatClick(): void {
this.statClick.emit();
}
}
<div class="card" [ngClass]="{ active: isActive }" (click)="onStatClick()"></div>

Parent Component

export class ParentComponent  {

public activeItem: ChartStat | null = null;

@Input() public isActive: boolean = false;

public onStatClick(clickedItem: ChartStat): void {
this.activeItem = clickedItem;
}
}
<app-child
[isActive]="item === activeItem"
(statClick)="onStatClick(item)"
/>

Filed Under: Other

Tab Panel with Output

import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject, Input, OnInit } from '@angular/core';
import { Tab } from '@app/lib/shared/models/tab.model';
import { MenuService } from '@app/lib/shared/services/menu.service';

import { TabType } from './tab.enum';

@Component({
selector: 'app-tab',
standalone: true,
imports: [CommonModule],
templateUrl: './tab.component.html',
styleUrl: './tab.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TabComponent implements OnInit {
private readonly _menuService = inject(MenuService);
@Input() public isDark: boolean = false;
@Input() public isActive: boolean = false;
@Input() public tabType: TabType = TabType.DEFAULT;
public TabType = TabType;
public mockData?: readonly Tab[];
public activeIndex: number | null = null;

public ngOnInit(): void {
this.mockData = this._menuService.getMockTab();
this.activeIndex = 0;
}

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

public get classes(): string[] {
const typeClass = `tab-${this.tabType}`;
const darkMode = this.isDark ? 'dark' : 'light';
return [typeClass, darkMode];
}

public get activeContent(): string | undefined {
return this.activeIndex !== null && this.mockData ? this.mockData[this.activeIndex].content : undefined;
}
}
<div class="tab-wrapper" [ngClass]="classes">
<ul class="tab">
<li
class="tab-item"
*ngFor="let item of mockData; let i = index"
[ngClass]="{ active: activeIndex === i }"
(click)="setActiveIndex(i)"
>
<button class="tab-item-link" [ngClass]="{ active: activeIndex === i }" href="">{{ item.title }}</button>
</li>
</ul>
<div class="tab-output">
{{ activeContent }}
</div>
</div>

Filed Under: Other

  • « Go to Previous Page
  • Page 1
  • Page 2
  • Page 3
  • Page 4
  • Page 5
  • Interim pages omitted …
  • Page 11
  • Go to Next Page »

Primary Sidebar

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