### 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 ""