Angular JS  Support Tool - Make File Attachment Option - Not Required

Adam Ehrenworth
Adam Ehrenworth used Ask the Experts™
I was handed over a support tool by our contractor that is based on Angular JS. I have some basic understanding of how it functions, but I am having trouble turning off a check on a particular module that is required that an attachment is included before the form can be submitted to a SharePoint list. I have included the code for the HTML as well as the associated code files. Please advise what need to be changed to allow for an attachment to be included but NOT have it be required.

<!--removed this from submit button [disabled]='!trgIssueForm.valid'-->
<div [@routerTransition] class="panel panel-primary">
    <!--<h2>Request Management- <small>{{_requestService?._selReq?.Title}}</small></h2>-->
    <div class="panel-heading">
    <div class="panel-body">
        <form class="form-horizontal"
              [formGroup]="trgIssueForm" >
                <div class="checkbox">
                    <label class="formyself">
                        <input type="checkbox" formControlName="MyRequest">This request is for myself
                <div class="form-group">
                    <mat-form-field class="col-md-6">
                        <input matInput class="form-control" 
                                formControlName="RequesterWWID" />
                                <mat-hint align="start">{{_impUser?.RequesterName}}</mat-hint>
                <!--<div class="form-group">
                    <mat-form-field class="col-md-6">
                        <input matInput class="form-control" 
                                placeholder="Requester Name" 
                                formControlName="RequesterName" />
                <div class="form-group">
                    <mat-form-field class="col-md-6">
                        <mat-select class="form-control"
                            placeholder="Select Issue Type"
                            <mat-option value="TrainingIssue">Completion issue</mat-option>
                            <mat-option value="TrainingIssue">Training not working</mat-option>
                            <mat-option value="TrainingIssue">Other</mat-option>
                            <!--<mat-option value="WhyTrg">Why did I get this assigned?</mat-option>-->
                <div class="form-group">
                    <mat-form-field class="col-md-6">
                        <input matInput [matAutocomplete]="auto"
                                formControlName="Training" />
                                <mat-autocomplete #auto="matAutocomplete">
                                    <mat-option *ngFor="let trg of filteredTrainings | async" [value]="trg.TrainingCode">
                                        <span>Title: {{trg.Title}}</span> | Code: <span>{{ trg.TrainingCode }}</span>
                <div class="form-group" [hidden]="_hideScreenshot">
                    <div class="col-md-6">
                        <input class="form-control file-attach" 
                            placeholder="Attache Screenshot" 
                            accept=".jpeg, .png"
                            formControlName="Screenshot" />
                        <!--<p *ngIf="trgIssueForm.controls.Screenshot.errors?.required" style="color: red">Screenshot is required!</p>-->
                        <label class="file-attach-label">
                            Attach a Screenshot
                <div class="form-group">
                    <mat-form-field class="col-md-6">
                        <textarea matInput class="form-control" 
                <div class="form-group">
                    <div class="col-md-4 col-md-offset-2">
                            <button mat-raised-button class="btn btn-primary"
                            <a class="btn btn-default"
        <div class='has-error' style="color: red" *ngIf='errorMessage'>{{errorMessage}}</div>
    <!--<ngb-alert [type]="alert.type" (close)="closeAlert(alert)" *ngFor="let alert of alerts">{{ alert.message }}</ngb-alert>-->

Open in new window

TS Components
import { Component, OnInit, AfterViewInit, OnDestroy, ViewChildren, ElementRef, Input } from '@angular/core';
import { FormBuilder, FormGroup, FormControl, FormArray, Validators, FormControlName } from '@angular/forms';
import { routerTransition } from '../../router.animations';
import { RequestService, FileValidator } from 'app/core';
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import { GenericValidator } from '../../shared/generic-validator';
import { TrgIssueService } from './trgissue.service';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import {startWith} from 'rxjs/operators/startWith';

import { DISABLED } from '@angular/forms/src/model';
import { HttpClient } from '@angular/common/http';
import { IImpUser } from 'app/reqmgmt/dtos';
import { map, filter, debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';

  selector: 'app-req-whytrg',
  templateUrl: './trgissue.component.html',
  animations: [routerTransition()]
export class TrgIssueComponent implements OnInit {

  currentUrl: string;
  @ViewChildren(FormControlName, { read: ElementRef }) formInputElements: ElementRef[];

  // pageTitle = 'Product Edit';
  // errorMessage: string;
  trgIssueForm: FormGroup;
  selTraining: any = null;
  public errorMessage = null;
  public _hideScreenshot = true;
  public _impUser: IImpUser = <IImpUser>{};
  // private sub: Subscription;
  MyRequest: FormControl = new FormControl();
  private isMyrequest = true;
  // MyRerequesterWWIDId: FormControl = new FormControl();
  // Use with the generic validation message class
  // displayMessage: { [key: string]: string } = {};
  // private validationMessages: { [key: string]: { [key: string]: string } };
  private genericValidator: GenericValidator;
  filteredTrainings: Observable<any[]>;
  constructor(private _router: Router, public _requestService: RequestService,
    private fb: FormBuilder,
    private _http: HttpClient,
    private route: ActivatedRoute,
    private router: Router,
    private _trgIssueService: TrgIssueService ) { => {
        if (event instanceof NavigationEnd ) {
            this.currentUrl = event.url;
            console.log('TrgIssueComponent- constructor - this.currentUrl', this.currentUrl);
    // this.genericValidator = new GenericValidator(this.validationMessages);
  filterTrainings(TrainingCode: string) {
    console.log('TrgIssueComponent -> filterTrainings: TrainingCode', TrainingCode);
      return this._requestService._distTrgs.filter(trg =>
        trg.TrainingCode.toLowerCase().indexOf(TrainingCode.toLowerCase()) === 0);
  buildForm() {
    this.trgIssueForm ={
      MyRequest: true,
      RequesterWWID: new FormControl(this._impUser.WWID, {
        validators: [Validators.required], updateOn: 'blur'}),
      // RequesterName: new FormControl(this._impUser.RequesterName, {
      //  validators: [Validators.required], updateOn: 'blur'}),
      ReqActSelect: ['', Validators.required],
      Screenshot: ['', FileValidator.validate],
      Training: ['', Validators.required],
      RequestDescription: ''
  ngOnInit() {
    console.log('TrgIssueComponent Component OnInit');
    // If user directly came to thispage, we need to get Selected Request Item
    if ( !this._requestService._selReq) {
      // filter this request type from reqTypes collection
      this._requestService._selReq = Object.assign({}, this._requestService._requestTypes.filter(req =>
        req.ReqLink === this.currentUrl)[0]);
        console.log('TrgIssueComponent->ngOnInit-_selReq',  this._requestService._selReq);
    // get all the data
    // this._requestService.getTrainings();
    // this.trgIssueForm.get('RequesterName').disable();
    this.filteredTrainings = this.trgIssueForm.get('Training').valueChanges
        map(trg => trg ? this.filterTrainings(trg) : this._requestService._distTrgs)
        .subscribe(value => this.onChangeMyRequest(value));
    .subscribe(value => this.onReqActionChange(value));
      subscribe(inputval => {
        console.log('TrgIssueComponent->ngOnInit- getWWIDDataByWWID -> inputval', inputval );
        if (!this.trgIssueForm.get('MyRequest').value) {
          this._requestService.getWWIDDataByWWID(inputval).then(res => {
            console.log('TrgIssueComponent->ngOnInit- getWWIDDataByWWID -> res', res);
            if (res && res.length === 1 ) {
              const tUser: IImpUser = <IImpUser>{};
              tUser.WWID = res[0].Title;
              tUser.RequesterName = res[0].FirstName + ' ' + res[0].LastName ;
              tUser.Email = res[0].Email;
              this._impUser = tUser;
              // this.trgIssueForm.get('RequesterName').setValue(this._impUser.RequesterName);
              console.log('TrgIssueComponent->ngOnInit- getWWIDDataByWWID -> this._impUser', this._impUser);
              // this.trgIssueForm.get('RequesterWWID').setValue(this._impUser.RequesterName);
            } else {
              this._impUser = null;
                'required': true });
  setImpUserToDefault() {
    const tUser: IImpUser = <IImpUser>{};
    tUser.WWID = this._requestService._userProfile.WWID;
    tUser.RequesterName = this._requestService._userProfile.DisplayName;
    tUser.Email = this._requestService._userProfile.Email;
    this._impUser = tUser;
onReqActionChange(action: String) {
  console.log('TrgIssueComponent -> onReqActionChange: action', action);
  if (action && action.toLowerCase() === 'whytrg') {
    this._hideScreenshot = true;
  } else if (action && action.toLowerCase() === 'trainingissue') {
    this._hideScreenshot = false;

  // (change)="onChangeMyRequest($"
  onChangeMyRequest(isChecked: boolean): void {
    console.log('TrgIssueComponent -> onChangeMyRequest: _impUser', isChecked, this._impUser);
    if ( isChecked ) {
      // Set ImpUser to default
      this.trgIssueForm.get('RequesterWWID').setValue(this._impUser.WWID, {emitEvent: false});
    } else {
      this._impUser = null;

  saveRequest(): void {
    console.log('TrgIssueComponent -> saveRequest -');
    if (this.trgIssueForm.dirty && this.trgIssueForm.valid) {
        // Copy the form values over the product object values
        const p = Object.assign({}, this.trgIssueForm.value);
        // get selected trainng detail
        this.selTraining = this._requestService._trgUgInv.filter(trg =>
          trg.TrainingCode.toLowerCase().indexOf(p.Training.toLowerCase()) === 0)[0];
          console.log('TrgIssueComponent -> saveRequest-> this.selTraining', this.selTraining);
        p['TrainingName'] = this.selTraining.TrainingName;
        p['TrainingCode'] = this.selTraining.TrainingCode;
        p['Title'] = this._requestService._selReq.Title + ' - ' + p['ReqActSelect'];
        p['ReqSupportMailbox'] = this._requestService._selReq.ReqSupportMailbox;
        p['RequesterWWID'] = this._impUser.WWID;
        p['RequesterName'] = this._impUser.RequesterName;
        p['RequesterEmail'] = this._impUser.Email;
        p['ReqContentTypeId'] = this._requestService._selReq.ReqContentTypeId;
        console.log('TrgIssueComponent -> saveRequest-> p', p);
          result => {
            console.log('TrgIssueComponent ->saveRequest- > result', result);
        error => {
          this.errorMessage = error;
          console.log('TrgIssueComponent ->saveRequest- > error', error);
cancelRequest(): void {
  /*ngAfterViewInit(): void {
    // Watch for the blur event from any input element on the form.
    const controlBlurs: Observable<any>[] = this.formInputElements
        .map((formControl: ElementRef) => Observable.fromEvent(formControl.nativeElement, 'blur'));

    // Merge the blur event observable with the valueChanges observable
    Observable.merge(this.trgIssueForm.valueChanges, ...controlBlurs).debounceTime(800).subscribe(value => {
        this.displayMessage = this.genericValidator.processMessages(this.trgIssueForm);

Open in new window

TS Services
import {Injectable, OnInit} from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
import { sp, Web } from "@pnp/sp";
import { environment } from '../../../environments/environment';
import { RequestService } from 'app/core';
import { Response } from '@angular/http';
import { fromPromise } from 'rxjs/observable/fromPromise';

export class TrgIssueService {
    constructor(private _reqService: RequestService) {
    // public _reqTypes;
    saveRequest(req): Observable<any> {
        console.log('TrgIssueService -> saveRequest->req', req);
        if (!req.ID) {
            return this.createItem(req);
        // return this.updateRequest(reqWhyTrg);
    private createItem(_req):  Observable<any> {
        console.log('TrgIssueService -> createRequest->_req', _req);
        let _result = null;
        return fromPromise(sp.web.lists.getByTitle('Requests').items.add({
            Title: _req.Title,
            Action: _req.ReqActSelect,
            TrainingCode: _req.Training,
            TrainingName: _req.TrainingName,
            ReqSupportMailbox: _req.ReqSupportMailbox,
            ContentTypeId: _req.ReqContentTypeId,
            RequestDescription: _req.RequestDescription,
            RequesterName: _req.RequesterName,
            RequesterEmail: _req.RequesterEmail,
            RequesterWWID: _req.RequesterWWID,
        }).then((iar: any) => {
            console.log('TrgIssueService -> createItem-> iar', iar);
            if (_req.Screenshot[0]) {
            return sp.web.lists.getByTitle('Requests').items.getById(
                attachmentFiles.add(_req.Screenshot[0].name, _req.Screenshot[0]).then(file => {
                    _result = file;
                    return _result;
            } else {
                //return iar;
    private updateRequest(_req) {
        let _result = null;
            Title: _req.AssignedTrg + ' - ' + _req.RequesterName,
            Training: _req.AssignedTrg,
            RequestDescription: _req.RequestDescription,
            RequesterName: _req.RequesterName,
            RequesterEmail: _req.RequestrEmail
        }).then((iar: any) => {
            console.log('TrgIssueService -> cre qateRequest-> iar', iar);
            _result = iar;
    private extractData(response: Response) {
        const body = response.json();
        return || {};
    private handleError(error: Response): Observable<any> {
        // in a real world app, we may send the server to some remote logging infrastructure
        // instead of just logging it to the console
        return Observable.throw(error.json().error || 'Server error');

Open in new window

Thanks for your help.
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Most Valuable Expert 2017
Distinguished Expert 2018

Firstup Angular JS is 1.x of Angular - the code above is Angular 2,4 or 5 (We leave the JS off).

Secondly the file attachement input (I am assuming it is the file input you are referring to) has this validator specified

 Screenshot: ['', FileValidator.validate],

Open in new window

Which is defined here
import { RequestService, FileValidator } from 'app/core';

Open in new window

So unless this validator does something other than check for existance you can simply remove it from the FormControl

 Screenshot: [''],

Open in new window

Adam EhrenworthLead Technology Analyst


Thank you for the clarification.

I made this change to the Form Control and attempted to submit a request without an attachment and it still will not proceed. No error is being triggered in console (when I view with F12).

If I attach a file and submit it works fine.

Is there something else I am missing?
Most Valuable Expert 2017
Distinguished Expert 2018
What does your updated code look like.

Note on line 137 of your Component .ts you have some logic that resets the Validator based on the action value - make sure you are not being undone by that code.
Adam EhrenworthLead Technology Analyst


Yes, that was the issue. I noticed that when I added your first suggestion.

Thank you for the help! I am slowly learning Angular based on an urgent business need - but hopefully, it will help me out in the future as well.
Most Valuable Expert 2017
Distinguished Expert 2018

You are welcome.

Good luck with it Angular is a great framework but it does take some time to get to grips with the ins and outs of it - hang in there.

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial