Angular 4/5 material raised button with input file - html

I am working on a angular application, currently to upload a file I am using this :
<label class="btn btn-default">
<input type="file" (change)="selectFile($event)">
</label>
<button class="btn btn-success" [disabled]="!selectedFiles"
(click)="upload()">Upload</button>
with methods on my .ts file, it is working well.
I want to upgrade this to material angular components raised button like that right now :
<button mat-raised-button>
<input type="file" (change)="selectFile($event)">
</button>
<button mat-button disabled [disabled]="!selectedFiles" (click)="upload()">Upload</button>
the disabled button is doing well but the input file part doesnt work , it prints baddly and does not open a file folder search window. any ideas?

Won't advise using input field within a button, better you hide the file input and then a button to trigger it. The below example will show a minimal example of it
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}} is for Uploading</h2>
</div>
<button mat-raised-button (click)="openInput()">
Select File to Upload
</button>
<input id="fileInput" hidden type="file" (change)="fileChange($event.target.files)" >
<button mat-button [disabled]="!ourFile" (click)="upload()">Upload</button>
`
})
export class App {
name:string;
ourFile: File; // hold our file
constructor() {
this.name = `Angular! v${VERSION.full}`
}
/**
* this is used to trigger the input
*/
openInput(){
// your can use ElementRef for this later
document.getElementById("fileInput").click();
}
fileChange(files: File[]) {
if (files.length > 0) {
this.ourFile = files[0];
}
}
/**
* this is used to perform the actual upload
*/
upload() {
console.log('sending this to server', this.ourFile);
}
}
Check this plnk
With the above example, you should be able to style your button without distorting HTML semantics

Related

Make custom file upload angular component required in the form

I have created a custom file upload angular component and I am using it in a template driven form. The form contains angular material components. I want this custom component to be required with the template driven form.
This is the html code for the custom component:
<input type="file" class="file-input" (change)="onFileSelected($event.target.files[0])" #fileUpload>
<div class="file-upload">
<button type="button" mat-mini-fab color="primary" class="upload-btn" (click)="fileUpload.click()">
<mat-icon>attach_file</mat-icon>
</button>
{{uploadedFileName || "Choose a file."}}
</div>
The typescript code:
uploadedFileName = "";
#Output() onUploadFileEvent = new EventEmitter<File>();
constructor() { }
ngOnInit(): void {
}
onFileSelected(uploadedFile: File) {
if (uploadedFile) {
this.uploadedFileName = uploadedFile.name;
this.onUploadFileEvent.emit(uploadedFile);
}
}
And I am using the component like this in the form:
<app-file-uploader (onUploadFileEvent)="uploadFile($event)"></app-file-uploader>
How can I make this custom component required and prevent the user from submitting the form if a file is not yet selected ?
Thanks;

ngf-select to Angular 8

I am migrating from Angular js to Angular 8. How can I migrate these lines of code to Angular 8:
<button class="other-button contactUsSpecific"
type="button" data-ngf-
select="contactCtrl.uploadFiles($file)"
data-ngf-max-size="9MB"
data-ngf-model-invalid="errorFiles">
<span [innerHTML]="tk.contactus.upload"></span>
</button>
I cannot find data-ngf equivalent over the internet for Angular 8.
You should have look at the answer:1 to get information on how to upload file using Angular 8.
We can add maximum size of file constraint by modifying the function call on change at file input field.
Eg:
app.component.ts
export class AppComponent {
files: FileList;
fileToUpload: File = null;
maxFileSizeBytes: number = 9e+6;
handleFileInput(event){
this.files = event.target.files;
this.fileToUpload = this.files[0];
if(this.fileToUpload.size < this.maxFileSizeBytes){
console.log("Less than required size, file will be uploaded.");
}else{
console.log("Exceeding Size");
}
}
app.component.html
<div class="form-group">
<label for="file">Choose File</label>
<input id="file" (change)="handleFileInput($event)" type="file">
</div>
Make service call in if else block. Attaching stackblitz for more clarity
Angular File Upload Demo

Add Data Dynamically to HTML Button in HTML page

I have this button, I need to add Pendo Data that is dynamically working based on which button we chose. Mostly this is making the Button unique. When I have a button that is not changing I add like this:
<button mat-button
 data-pendo="pendo-prospects-send-application"
class='round-button'
color='primary'
type='button'
.....>
</button>
But sometime I need to add this data to one button that is changing based on CSS class. I am not sure how check for that.
For example I need to add to a button when :
if [class.fa-pencil] then data-pendo "Something"
if [class.fa-plus] then data-pendo "Something else"
This is the button that changes base on class:
<button mat-button
class='round-button'
type='button'
[class.disabled-button]='GuidId'
color='primary'
(click)='onAssignLoanOfficer()'>
<i class='fal'
[class.fa-pencil]='GuidId'
[class.fa-plus]='!GuidId'></i>
</button>
How I can do that?
Based on your comments I think this is what you want to do:
<button
(click)="onAssignLoanOfficer()"
[class.disabled-button]="GuidId"
[attr.data-pendo]="GuidId ? 'pendo-edit-loan' : 'pendo-add-loan'"
class="round-button"
color="primary"
type="button">
<i [class.fa-pencil]="GuidId"
[class.fa-plus]="!GuidId"
class="fall">
</i>
</button>
ALTERNATIVE:
As a more sophisticated alternative (I really don't know how you intend to use what you're asking for), you can build a directive to add the attribute you want based on a map of class-to-pendo-data conversion information:
#Directive({
selector: '[addPendoData]'
})
export class AddPendoDataDirective implements AfterViewInit {
constructor(private _el: ElementRef, private _renderer: Renderer2) {}
ngAfterViewInit() {
const pendoData: string | null | undefined = this._getPendoValue();
if (!pendoData) { return;}
const $button: HTMLElement = this._el.nativeElement;
this._renderer.setAttribute($button, 'data-pendo', pendoData);
}
private _getPendoValue() {
const $child: HTMLElement = this._el.nativeElement;
if(!$child) { return null; }
const $i: HTMLElement = $child.querySelector('i');
if(!$i) { return null; }
const listOfClasses: string[] = $i.className.split(' ');
if (!(listOfClasses && listOfClasses.length)) { return null; }
for(const className of listOfClasses) {
if(PENDO_MAP[className]) { return PENDO_MAP[className]; }
}
return null;
}
}
const PENDO_MAP: { [className: string]: string } = {
'fa-pencil': 'pendo-edit-loan',
'fa-plus': 'pendo-add-loan'
// add other mappings here...
};
and you can use it like this:
<button
(click)="onAssignLoanOfficer()"
[class.disabled-button]="GuidId"
addPendoData
class="round-button"
color="primary"
type="button">
<i [class.fa-pencil]="GuidId"
[class.fa-plus]="!GuidId"
class="fall">
</i>
</button>
I've put together this stackblitz demo.
You could define a directive, as julianobrasil suggests.
I want to point a different way to achieve this using Angular templates.
Define two buttons, addition and edition, separately.
Put a default where you want to be rendered
<div class="action-button" *ngIf="GuidId">
<!-- Add button -->
<button mat-button
data-pendo="pendo-add-loan"
(click)="add(...)"
class="round-button"
color="primary">
<i class="fal fa-plus"></i>
</button>
</div>
Then, define a ng-template tag with the other button definition. A completely fresh new.
<ng-template #edit-button>
<div class="action-button">
<!-- Edit button -->
<button mat-button
(click)="edit(...)"
data-pendo="pendo-edit-loan"
class="round-button"
color="primary">
<i class="fal fa-pencil"></i>
</button>
</div>
</ng-template>
Finally, just change the *ngIf statement to render either an addition button or an edition one.
<div class="action-button" *ngIf="!GuidId else edit-button">
<!-- Add button here -->
</div>
This is a way that scales in order to keep components isolated. The buttons are not related anymore, so you can implement w/o having to condition every style, action, etc.
Hope it helps.

I am facing some issues regarding the form submission

I want to submit the form on the button click as well as navigate to the next page, but it shows an error : "Form submission canceled because the form is not connected".
Can anyone help me with this problem ?? I am using nebular.
This is the html code
<nb-step [label]="labelOne" [stepControl]="formOne">
<ng-template #labelOne>Device type</ng-template>
<form class="form-inline" #formOne="ngForm" >
// Code goes here...
<div class="buttonHolder">
<button nbButton routerLink="/dashboard" nbStepperNext>Cancel</button>
<button nbButton outline status="primary" (ngSubmit)="onSubmit(formOne)"
nbStepperNext [disabled]="!formOne.valid">Next</button>
</div>
</form>
</nb-step>`
This is the .ts code
onSubmit(form: NgForm) { console.log(form.value); form.reset(); }
To change page after submitting the form try to import Router and register it in constructor(private _router: Router) in your .ts file.
At the end of onSubmit() method add this._router.navigateByUrl('/pathOfYourNextPage'); and in this way you'll make redirect throught your .ts file.
Hope that will help!
You can try to below way
ts file
onSubmit(formOne: NgForm) {
console.log(formOne.value);
this._router.navigate(['/navigation page link']); // navigation url link
formOne.reset();
}
constructor(){
private _router: Router,
}
HTML file
<nb-step [label]="labelOne" [stepControl]="formOne">
<ng-template #labelOne>Device type</ng-template>
<form class="form-inline" #formOne="ngForm" (ngSubmit)="onSubmit(formOne)" >
// Code goes here...
<div class="buttonHolder">
<button nbButton routerLink="/dashboard" nbStepperNext>Cancel</button>
<button nbButton type= "submit" outline status="primary"
nbStepperNext [disabled]="!formOne.valid">Next</button>
</div>
</form>

Performing Click on an ElementRef object in Angular 4 [duplicate]

I am trying to fire click event (or any other event) on element programatically , In other word I want to know the similar features as offered by jQuery .trigger() method in angular2.
Is there any built in method to do this? ..... if not please suggest how can i do this
Consider the following code fragment
<form [ngFormModel]="imgUploadFrm"
(ngSubmit)="onSubmit(imgUploadFrm)">
<br>
<div class="input-field">
<input type="file" id="imgFile" (click)="onChange($event)" >
</div>
<button id="btnAdd" type="submit" (click)="showImageBrowseDlg()" )>Add Picture</button>
</form>
Here when user click the btnAdd it should fire the click event on imgFile
Angular4
Instead of
this.renderer.invokeElementMethod(
this.fileInput.nativeElement, 'dispatchEvent', [event]);
use
this.fileInput.nativeElement.dispatchEvent(event);
because invokeElementMethod won't be part of the renderer anymore.
Angular2
Use ViewChild with a template variable to get a reference to the file input, then use the Renderer to invoke dispatchEvent to fire the event:
import { Component, Renderer, ElementRef, ViewChild } from '#angular/core';
#Component({
...
template: `
...
<input #fileInput type="file" id="imgFile" (click)="onChange($event)" >
...`
})
class MyComponent {
#ViewChild('fileInput') fileInput:ElementRef;
constructor(private renderer:Renderer) {}
showImageBrowseDlg() {
// from http://stackoverflow.com/a/32010791/217408
let event = new MouseEvent('click', {bubbles: true});
this.renderer.invokeElementMethod(
this.fileInput.nativeElement, 'dispatchEvent', [event]);
}
}
Update
Since direct DOM access isn't discouraged anymore by the Angular team this simpler code can be used as well
this.fileInput.nativeElement.click()
See also https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent
I also wanted similar functionality where I have a File Input Control with display:none and a Button control where I wanted to trigger click event of File Input Control when I click on the button, below is the code to do so
<input type="button" (click)="fileInput.click()" class="btn btn-primary" value="Add From File">
<input type="file" style="display:none;" #fileInput/>
as simple as that and it's working flawlessly...
This worked for me:
<button #loginButton ...
and inside the controller:
#ViewChild('loginButton') loginButton;
...
this.loginButton.getNativeElement().click();
To get the native reference to something like an ion-input, ry using this
#ViewChild('fileInput', { read: ElementRef }) fileInput: ElementRef;
and then
this.fileInput.nativeElement.querySelector('input').click()
Günter Zöchbauer's answer is the right one. Just consider adding the following line:
showImageBrowseDlg() {
// from http://stackoverflow.com/a/32010791/217408
let event = new MouseEvent('click', {bubbles: true});
event.stopPropagation();
this.renderer.invokeElementMethod(
this.fileInput.nativeElement, 'dispatchEvent', [event]);
}
In my case I would get a "caught RangeError: Maximum call stack size exceeded" error if not. (I have a div card firing on click and the input file inside)
If you want to imitate click on the DOM element like this:
<a (click)="showLogin($event)">login</a>
and have something like this on the page:
<li ngbDropdown>
<a ngbDropdownToggle id="login-menu">
...
</a>
</li>
your function in component.ts should be like this:
showLogin(event) {
event.stopPropagation();
document.getElementById('login-menu').click();
}