### File

`src/lib/upload-button/upload-button.component.ts`

### Implements

`ControlValueAccessor` `MatFormFieldControl` `OnDestroy`

### Metadata

changeDetection | `ChangeDetectionStrategy.OnPush`
host | `{ }`
providers | `{ provide: MatFormFieldControl, useExisting: UploadButtonComponent, }`
selector | `rxap-upload-button`
imports | `MatButtonModule` `CdkOverlayOrigin` `MethodDirective` `MatIconModule` `MatProgressSpinnerModule` `MatTooltipModule` `CdkConnectedOverlay` `AsyncPipe` `ReadAsDataURLPipe` `MatSnackBarModule`
styleUrls | `./upload-button.component.scss`
templateUrl | `./upload-button.component.html`

### Index

###### Properties
Public accept Readonly acceptAttribute Public allowDownload Optional autofilled Optional controlType errorState focused Public isImageUploaded Static nextId Public Readonly ngControl Public positions shouldLabelFloat Public Readonly snackBar Public stateChanges touched Public uploaded Optional userAriaDescribedBy Public value
###### Methods
clear download Public onContainerClick onFileInputChange onFocusIn onFocusOut Public openOverlay Public registerOnChange Public registerOnTouched Public setDescribedByIds Public setDisabledState Public writeValue
###### Inputs
disabled placeholder required allowDownload
###### Outputs
uploaded
###### HostListeners
focusin focusout
###### Accessors
placeholder empty id isImage

### Constructor

`constructor()`
Defined in src/lib/upload-button/upload-button.component.ts:135

### Inputs

disabled
Type : `boolean`
Default value : `false`
Defined in src/lib/upload-button/upload-button.component.ts:96

placeholder
Type : `string`
Defined in src/lib/upload-button/upload-button.component.ts:116

required
Type : `boolean`
Default value : `false`
Defined in src/lib/upload-button/upload-button.component.ts:102

allowDownload
Default value : `false`
Defined in src/lib/upload-button/upload-button.component.ts:78

### Outputs

uploaded
Defined in src/lib/upload-button/upload-button.component.ts:80

### HostListeners

focusin
Arguments : `'$event'`
`focusin(event: FocusEvent)`
Defined in src/lib/upload-button/upload-button.component.ts:189

focusout
Arguments : `'$event'`
`focusout(event: FocusEvent)`
Defined in src/lib/upload-button/upload-button.component.ts:197

### Methods

clear
`clear()`
Defined in src/lib/upload-button/upload-button.component.ts:234
Returns : `void`

download
`download()`
Defined in src/lib/upload-button/upload-button.component.ts:208
Returns : `void`

Public onContainerClick
`onContainerClick(event: MouseEvent)`
Defined in src/lib/upload-button/upload-button.component.ts:149
Parameters :
Name | Type | Optional
event | `MouseEvent` | No
Returns : `void`

onFileInputChange
`onFileInputChange($event: any)`
Defined in src/lib/upload-button/upload-button.component.ts:219
Parameters :
Name | Type | Optional
$event | `any` | No
Returns : `void`

onFocusIn
`onFocusIn(event: FocusEvent)`
Decorators : `@HostListener('focusin', ['$event'])`
Defined in src/lib/upload-button/upload-button.component.ts:189
Parameters :
Name | Type | Optional
event | `FocusEvent` | No
Returns : `void`

onFocusOut
`onFocusOut(event: FocusEvent)`
Decorators : `@HostListener('focusout', ['$event'])`
Defined in src/lib/upload-button/upload-button.component.ts:197
Parameters :
Name | Type | Optional
event | `FocusEvent` | No
Returns : `void`

Public openOverlay
`openOverlay()`
Defined in src/lib/upload-button/upload-button.component.ts:182
Returns : `void`

Public registerOnChange
`registerOnChange(fn: any)`
Defined in src/lib/upload-button/upload-button.component.ts:164
Parameters :
Name | Type | Optional
fn | `any` | No
Returns : `void`

Public registerOnTouched
`registerOnTouched(fn: any)`
Defined in src/lib/upload-button/upload-button.component.ts:168
Parameters :
Name | Type | Optional
fn | `any` | No
Returns : `void`

Public setDescribedByIds
`setDescribedByIds(ids: string[])`
Defined in src/lib/upload-button/upload-button.component.ts:153
Parameters :
Name | Type | Optional
ids | `string[]` | No
Returns : `void`

Public setDisabledState
`setDisabledState(isDisabled: boolean)`
Defined in src/lib/upload-button/upload-button.component.ts:172
Parameters :
Name | Type | Optional
isDisabled | `boolean` | No
Returns : `void`

Public writeValue
`writeValue(file: File)`
Defined in src/lib/upload-button/upload-button.component.ts:177
Parameters :
Name | Type | Optional
file | `File` | No
Returns : `void`

### Properties

Public accept
Default value : `input<string | string[]>('**/**')`
Defined in src/lib/upload-button/upload-button.component.ts:74

Readonly acceptAttribute
Default value : `computed(() => Array.isArray(this.accept) ? this.accept.join(',') : this.accept)`
Defined in src/lib/upload-button/upload-button.component.ts:76

Public allowDownload
Default value : `false`
Defined in src/lib/upload-button/upload-button.component.ts:78

Optional autofilled
Type : `boolean | undefined`
Defined in src/lib/upload-button/upload-button.component.ts:97

Optional controlType
Type : `string | undefined`
Default value : `'rxap-upload-button'`
Defined in src/lib/upload-button/upload-button.component.ts:98

errorState
Default value : `false`
Defined in src/lib/upload-button/upload-button.component.ts:99

focused
Default value : `false`
Defined in src/lib/upload-button/upload-button.component.ts:100

Public isImageUploaded
Default value : `signal(false)`
Defined in src/lib/upload-button/upload-button.component.ts:82

Static nextId
Type : `number`
Default value : `0`
Defined in src/lib/upload-button/upload-button.component.ts:72

Public Readonly ngControl
Default value : `inject(NgControl, { optional: true, self: true, })`
Defined in src/lib/upload-button/upload-button.component.ts:107

Public positions
Type : `ConnectedPosition[]`
Default value : `[ { originY: 'bottom', originX: 'start', overlayX: 'start', overlayY: 'top', offsetY: 12, }, ]`
Defined in src/lib/upload-button/upload-button.component.ts:83

shouldLabelFloat
Default value : `true`
Defined in src/lib/upload-button/upload-button.component.ts:103

Public Readonly snackBar
Default value : `inject(MatSnackBar)`
Defined in src/lib/upload-button/upload-button.component.ts:131

Public stateChanges
Default value : `new Subject<void>()`
Defined in src/lib/upload-button/upload-button.component.ts:104

touched
Default value : `false`
Defined in src/lib/upload-button/upload-button.component.ts:92

Public uploaded
Defined in src/lib/upload-button/upload-button.component.ts:80

Optional userAriaDescribedBy
Type : `string | undefined`
Defined in src/lib/upload-button/upload-button.component.ts:105

Public value
Type : `File | null`
Default value : `null`
Defined in src/lib/upload-button/upload-button.component.ts:106

### Accessors

placeholder
get `placeholder()`
Defined in src/lib/upload-button/upload-button.component.ts:112
set `placeholder(plh)`
Defined in src/lib/upload-button/upload-button.component.ts:116
Parameters :
Name | Optional
plh | No
Returns : `void`

empty
get `empty()`
Defined in src/lib/upload-button/upload-button.component.ts:120

id
get `id()`
Defined in src/lib/upload-button/upload-button.component.ts:123

isImage
get `isImage()`
Defined in src/lib/upload-button/upload-button.component.ts:143

    import {
      CdkConnectedOverlay,
      CdkOverlayOrigin,
      ConnectedPosition,
    } from '@angular/cdk/overlay';
    import {
      AsyncPipe,
      DOCUMENT,
      NgIf,
    } from '@angular/common';
    import {
      ChangeDetectionStrategy,
      ChangeDetectorRef,
      Component,
      computed,
      ElementRef,
      EventEmitter,
      HostListener,
      inject,
      input,
      Input,
      OnDestroy,
      output,
      Output,
      signal,
    } from '@angular/core';
    import {
      ControlValueAccessor,
      NgControl,
    } from '@angular/forms';
    import { MatButtonModule } from '@angular/material/button';
    import { MatFormFieldControl } from '@angular/material/form-field';
    import { MatIconModule } from '@angular/material/icon';
    import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
    import {
      MatSnackBar,
      MatSnackBarModule,
    } from '@angular/material/snack-bar';
    import { MatTooltipModule } from '@angular/material/tooltip';
    import { MethodDirective } from '@rxap/directives';
    import { Subject } from 'rxjs';
    import { ReadAsDataURLPipe } from './read-as-data-url.pipe';

    @Component({
        selector: 'rxap-upload-button',
        templateUrl: './upload-button.component.html',
        styleUrls: ['./upload-button.component.scss'],
        changeDetection: ChangeDetectionStrategy.OnPush,

        host: { class: 'rxap-upload-button' },
        providers: [
            {
                provide: MatFormFieldControl,
                useExisting: UploadButtonComponent,
            },
        ],
        imports: [
            MatButtonModule,
            CdkOverlayOrigin,
            MethodDirective,
            MatIconModule,
            MatProgressSpinnerModule,
            MatTooltipModule,
            CdkConnectedOverlay,
            AsyncPipe,
            ReadAsDataURLPipe,
            MatSnackBarModule,
        ]
    })
    export class UploadButtonComponent implements ControlValueAccessor, MatFormFieldControl<File>, OnDestroy {

      static nextId = 0;

      public accept = input<string | string[]>('**/**');

      readonly acceptAttribute = computed(() => Array.isArray(this.accept) ? this.accept.join(',') : this.accept);

      public allowDownload = input(false);

      public uploaded = output<File>();

      public isImageUploaded = signal(false);
      public positions: ConnectedPosition[] = [
        {
          originY: 'bottom',
          originX: 'start',
          overlayX: 'start',
          overlayY: 'top',
          offsetY: 12,
        },
      ];
      touched = false;

      // region MatFormFieldControl
      @Input()
      public disabled = false;
      autofilled?: boolean | undefined;
      controlType?: string | undefined = 'rxap-upload-button';
      errorState = false;
      focused = false;
      @Input()
      public required = false;
      shouldLabelFloat = true;
      public stateChanges = new Subject<void>();
      userAriaDescribedBy?: string | undefined;
      public value: File | null = null;
      public readonly ngControl = inject(NgControl, {
        optional: true,
        self: true,
      });
      private _placeholder!: string;
      get placeholder() {
        return this._placeholder;
      }
      @Input()
      set placeholder(plh) {
        this._placeholder = plh;
        this.stateChanges.next();
      }
      public get empty(): boolean {
        return this.value === null;
      }
      public get id(): string {
        return `rxap-upload-button-${ UploadButtonComponent.nextId++ }`;
      }
      // endregion

      private onChange?: (file: File) => any;
      private onTouched?: () => any;

      public readonly snackBar = inject(MatSnackBar);

      private readonly cdr = inject(ChangeDetectorRef);
      private readonly _elementRef = inject(ElementRef);
      private readonly document = inject(DOCUMENT);

      constructor() {
        if (this.ngControl != null) {
          this.ngControl.valueAccessor = this;
        }
      }

      get isImage(): boolean {
        return !!this.value?.type.match(/^image\//);
      }

      // region MatFormFieldControl
      // eslint-disable-next-line @typescript-eslint/no-empty-function
      public onContainerClick(event: MouseEvent): void {

      }

      public setDescribedByIds(ids: string[]): void {
        this._elementRef
            .nativeElement
            .querySelector('button')
            ?.setAttribute('aria-describedby', ids.join(' '));
      }

      public ngOnDestroy() {
        this.stateChanges.complete();
      }

      public registerOnChange(fn: any): void {
        this.onChange = fn;
      }

      public registerOnTouched(fn: any): void {
        this.onTouched = fn;
      }

      public setDisabledState(isDisabled: boolean) {
        this.disabled = isDisabled;
      }
      // endregion

      public writeValue(file: File): void {
        this.value = file;
        this.cdr.detectChanges();
      }

      public openOverlay() {
        if (this.value?.type.match(/^image\//)) {
          this.isImageUploaded.set(true);
        }
      }

      @HostListener('focusin', [ '$event' ])
      onFocusIn(event: FocusEvent) {
        if (!this.focused) {
          this.focused = true;
          this.stateChanges.next();
        }
      }

      @HostListener('focusout', [ '$event' ])
      onFocusOut(event: FocusEvent) {
        if (!this._elementRef.nativeElement.contains(event.relatedTarget as Element)) {
          this.touched = true;
          this.focused = false;
          if (this.onTouched) {
            this.onTouched();
          }
          this.stateChanges.next();
        }
      }

      download() {
        if (this.value) {
          const elm = this.document.createElement('a');
          this.document.body.appendChild(elm);
          elm.href = URL.createObjectURL(this.value);
          elm.setAttribute('download', this.value.name);
          elm.click();
          this.document.body.removeChild(elm);
        }
      }

      onFileInputChange($event: any) {
        const files: { [key: string]: File } = $event?.target?.files as any ?? {};
        const file = Object.values(files)[0];
        if (!file) {
          this.snackBar.open('No file selected', 'close', { duration: 3000 });
        } else {
          this.uploaded.emit(file);
          this.value = file;
          if (this.onChange) {
            this.onChange(file);
          }
          this.stateChanges.next();
        }
      }

      clear() {
        this.value = null;
      }

    }

    <div class="flex flex-row gap-2 items-center">
      <button
        #trigger="cdkOverlayOrigin"
        [disabled]="disabled"
        (click)="fileInput.click()"
        cdkOverlayOrigin
        class="grow-0"
        mat-stroked-button
        type="button">
        <span class="flex flex-row justify-center items-center gap-3">
          <mat-icon class="grow-0" svgIcon="upload"></mat-icon>&nbsp;
          @if (value) {
            <span class="truncate grow-0">{{ value.name }}</span>
          } @else {
            @if (placeholder) {
              <span>{{ placeholder }}</span>
            }
            <ng-content></ng-content>
          }
        </span>
      </button>
      @if (isImage) {
        <button (click)="openOverlay()"
                class="grow-0"
                i18n-matTooltip
                mat-icon-button
                matTooltip="Open image preview"
                type="button">
          <mat-icon>preview</mat-icon>
        </button>
      }
      @if (allowDownload() && value) {
        <button (click)="download()" class="grow-0" i18n-matTooltip mat-icon-button
                matTooltip="Download file" type="button">
          <mat-icon svgIcon="download"></mat-icon>
        </button>
      }
    </div>

    <ng-template
      (backdropClick)="isImageUploaded.set(false)"
      [cdkConnectedOverlayHasBackdrop]="true"
      [cdkConnectedOverlayOpen]="isImageUploaded()"
      [cdkConnectedOverlayOrigin]="trigger"
      [cdkConnectedOverlayPositions]="positions"
      cdkConnectedOverlay
    >
      <div class="bg-white dark:bg-black drop-shadow-xl overlay-container p-2 rounded overflow-auto">
        <img [src]="value | readAsDataURL | async" class="w-full">
      </div>
    </ng-template>

    <input #fileInput (change)="onFileInputChange($event)" [accept]="acceptAttribute()" [id]="id" class="hidden"
           type="file">

`./upload-button.component.scss`

    .overlay-container {
      min-width: 256px;
      max-width: 50vw;
      max-height: 50vh;
    }

Legend
Html element
Component
Html element with directive

# results matching ""

# No results matching ""