3

I have an Angular 5 app that works without problems when I run it in test mode, that is, when I run the application locally using the ng serve command. However, when I deploy the application in production mode, that is, through the command ng build --prod, the application exports without problems, but when I use the application in a productive environment, this problem appears: (these are images from the browsers console from google and safari)

Google Chrome Console:

enter image description here

Safari Console:

enter image description here

However, if I now export the application using the following command ng build --prod --aot false the application works without any problem.

Next I am going to show the component involved in the commented error:

user-online-quote.component.html

<app-navbar [loggedIn]=loggedIn></app-navbar>
<app-dynamic-form [answers$]="answers$"></app-dynamic-form>

user-online-quote.component.ts

import { Component, OnInit } from '@angular/core';
import { DynamicFormService } from '../_services/dynamic-form.service';
import { RadioQuestion }  from '../shared/_shared/answer-radio';
import { Observable } from "rxjs/Rx";
import { AuthService } from '../_services/auth.service';
import { Router, ActivatedRoute, Params } from '@angular/router';

@Component({
  selector: 'app-user-online-quote',
  templateUrl: './user-online-quote.component.html',
  styleUrls: ['./user-online-quote.component.css'],
  providers:  [DynamicFormService, AuthService]
})
export class UserOnlineQuoteComponent implements OnInit {
  totalPage:number;
  actualPage:number;
  percentageCompletion:number;
  currentQuestion:string;
  selectedOption:string;
  imageSource:string;
  loggedIn:boolean;
  formId:string;
  //public answers: any[];

  //Signo $ porque es un observable
  public answers$: Observable<any[]>;

  constructor(private service: DynamicFormService, private authService: AuthService,
    private activatedRoute: ActivatedRoute) {

   }

  ngOnInit() {
    this.loggedIn=this.authService.isLoggedIn();
    this.activatedRoute.queryParams.subscribe((params: Params) => {
      this.formId=params['form'];
      this.answers$=this.service.getAnswers(this.formId);
    });
    //this.answers=this.service.getAnswers("CAR00PR");
    this.totalPage=4;
    this.actualPage=1;
    this.calculateAdvancePercentage();
    this.currentQuestion="¿Pregunta de prueba?";
  }

  calculateAdvancePercentage(){
    this.percentageCompletion=(this.actualPage/this.totalPage)*100;
  }

  next(){
    this.actualPage=(this.actualPage+1);
    this.calculateAdvancePercentage();
  }

  back(){
    this.actualPage=(this.actualPage-1);
    this.calculateAdvancePercentage();
  }

}

dinamic-form.component.html

<div class="space"></div>
<ngx-loading [show]="loading" [config]="{ backdropBorderRadius: '0px', fullScreenBackdrop:true }"></ngx-loading>
<div *ngIf="submitted===false" class="container">
  <div class="row">
  <div class="col-lg-2">
    <div class="mx-2"></div>
  </div>
  <div class="col-lg-8">
    <div>
      <p>{{currentQuestion.description}}</p>
    </div>
    <div class="progress">
      <div class="progress-bar" role="progressbar" [style.width]="percentageCompletion + '%'" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></div>
    </div>
      <hr> 
      <div>
        <form [formGroup]="form">
          <div *ngFor="let answer of answers$ | async">
            <div [ngSwitch]="answer.controlType">
              <div *ngSwitchCase="'radio'">
                <div class="radios checkbox-group">
                  <div [hidden]="!(currentQuestion.id===answer.parent)" class="col-lg-12">
                    <input [formControlName]="answer.controlName" [id]="answer.id" type="radio" [value]="answer.label" (click)="next(null,answer.child)">
                      <label [for]="answer.id" class="button-label">
                        {{answer.label}}  
                      </label>
                  </div>
                </div>
              </div>
              <div *ngSwitchCase="'radio_textbox'">
                <div class="radios checkbox-group">
                  <div [hidden]="!(currentQuestion.id===answer.parent)" class="col-lg-12">
                    <input [formControlName]="answer.controlName" [id]="answer.id" type="radio" (click)="showTextbox ? showTextbox = false : showTextbox = true;">
                      <label [for]="answer.id" class="button-label">
                        {{answer.label}}  
                      </label>
                  </div>
                </div>
                  <div [hidden]="!(currentQuestion.id===answer.parent)" class="input-group mb-3 col-lg-12">
                    <input [hidden]="!showTextbox" type="text" class="form-control" [placeholder]="answer.placeholder" [formControlName]="answer.controlName" [id]="answer.id">
                      <div class="input-group-append">
                        <button [hidden]="!showTextbox" class="btn btn-outline-secondary" type="button" (click)="next(null,answer.child)">Siguiente</button>
                      </div>
                  </div>
              </div>
              <div *ngSwitchCase="'file'">
                <div [hidden]="!(currentQuestion.id===answer.parent)" class="col-lg-12">
                    <div class="form-group">
                        <label [for]="answer.id">{{answer.label}}</label>
                        <div class="custom-file">
                            <input [formControlName]="answer.controlName" type="file" class="custom-file-input" [id]="answer.id" lang="es" (change)="onFileChange($event)" #fileInput>
                            <label class="custom-file-label" for="customFileLang">{{fileName}}</label>
                        </div>
                        <div *ngIf="answer.required===true">
                          <small *ngIf="form.get(answer.controlName).status=='INVALID'" class="form-text text-muted-error">Debes tener una imagen de referencia para poder continuar</small>
                        </div>
                    </div>
                  <div class="my-4"></div>
                </div>
              </div>    
              <div *ngSwitchCase="'file_next'">
                <div [hidden]="!(currentQuestion.id===answer.parent)" class="col-lg-12">
                    <div class="form-group">
                        <label [for]="answer.id">{{answer.label}}</label>
                        <div class="custom-file">
                            <input [formControlName]="answer.controlName" type="file" class="custom-file-input" [id]="answer.id" lang="es" (change)="onFileChange($event)" #fileInput>
                            <label class="custom-file-label" for="customFileLang">{{fileName}}</label>
                        </div>
                        <div *ngIf="answer.required===true">
                          <small *ngIf="form.get(answer.controlName).status=='INVALID'" class="form-text text-muted-error">Debes tener una imagen de referencia para poder continuar</small>
                        </div>
                    </div>
                    <div class="my-4"></div>
                    <div class="text-center">
                      <button
                        (click)='next(answer.validations,answer.next)'
                        class="btn btn-outline-secondary">
                        Siguiente
                      </button>
                    </div>
                </div>
              </div>
              <div *ngSwitchCase="'textbox'" [hidden]="!(currentQuestion.id===answer.parent)" class="col-md-6 mb-3">
                <label [for]="answer.id">{{answer.label}}</label>
                <input [formControlName]="answer.controlName" type="text" class="form-control" [id]="answer.id" [placeholder]="answer.placeholder">
                <div *ngIf="answer.required===true">
                  <small *ngIf="form.get(answer.controlName).status=='INVALID'" class="form-text text-muted-error">Este campo es obligatorio</small>
                </div>
              </div>
              <div *ngSwitchCase="'textbox_next'" [hidden]="!(currentQuestion.id===answer.parent)">
                <div class="col-md-6 mb-3">
                  <label [for]="answer.id">{{answer.label}}</label>
                  <input [formControlName]="answer.controlName" type="text" class="form-control" [id]="answer.id" [placeholder]="answer.placeholder">
                  <div *ngIf="answer.required===true">
                    <small *ngIf="form.get(answer.controlName).status=='INVALID'" class="form-text text-muted-error">Este campo es obligatorio</small>
                  </div>
                </div>
                <div class="my-4"></div>
                <div class="text-center">
                  <button
                    (click)='next(answer.validations,answer.next)'
                    class="btn btn-outline-secondary">
                    Siguiente
                  </button>
                </div>
              </div>
              <div *ngSwitchCase="'textarea'">
                <div class="form-group col-lg-12" [hidden]="!(currentQuestion.id===answer.parent)">
                  <label for="description">{{answer.label}}</label>
                  <textarea [formControlName]="answer.controlName" class="form-control" id="description" rows="3"></textarea>
                </div>
                <div class="my-4"></div>
              </div>
            </div>
          </div>

        </form>

      </div>

      <div class="my-4"></div>
      <div class="text-center">
        <button 
          (click)='back()' 
          class="btn btn btn-outline-secondary">
            Atrás
        </button>
        <button 
          [hidden]="currentQuestion.last==true"
          (click)='cancel()' 
          class="btn btn btn-outline-secondary">
            Cancelar
        </button>
        <button 
          [hidden]="currentQuestion.last==false"
          [disabled]="form.invalid"
          type="submit"
          class="btn btn-outline-success"
          (click)="onSubmit()">
            Enviar Solicitud
        </button>
      </div>
  </div>
  <div class="col-lg-2">
      <div class="mx-2"></div>
  </div>
</div>
</div>
<div *ngIf="submitted==true" class="container">
  <div class="space">
      <div class="container">
          <div class="row">
              <div class="col-lg-1"></div>
              <div class="col-lg-10 justify">
                <p>Tu solicitud se ha enviado correctamente, debes esperar un máximo de 2 días para que el experto elabore una cotización
                  de acuerdo a tus requerimientos. 
                </p>
                <div class="d-flex justify-content-center">
                  <a [routerLink]="['/dashboard_usuario/negociaciones/en_proceso']">Ver negociación</a>
                </div>
              </div>
              <div class="col-lg-1"></div>
          </div>
      </div>
  </div>
</div>

dinamic-form.component.ts

import { Component, Input, OnInit, ChangeDetectorRef }  from '@angular/core';
import { FormGroup }                 from '@angular/forms';
import { AnswerBase }              from '../shared/_shared/answer-base';
import { AnswerControlService }    from '../_services/answer-control.service';
import { DynamicFormService } from '../_services/dynamic-form.service';
import { Question } from '../_models/question';
import { Observable } from "rxjs/Rx"
import { Router, ActivatedRoute, Params } from '@angular/router';
import { QuoteDto } from "../_dtos/quoteDto";
import { HttpClient, HttpResponse } from '@angular/common/http';
import {Location} from '@angular/common';


@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html',
  styleUrls: ['./dynamic-form.component.css'],
  providers: [ AnswerControlService ]
})
export class DynamicFormComponent implements OnInit {
  @Input() answers$: Observable<AnswerBase<any>[]>;
  form: FormGroup;
  payLoad = '';
  questionsList : Map<string, Question>;
  totalPage:number;
  actualPage:number;
  percentageCompletion:number;
  currentQuestion:Question;
  previousQuestion:Question;
  backwardQuestions:Array<string>;
  loading:boolean;
  fileName:string;
  photo:File;
  image:any;
  nextDisable:boolean;
  userId:string;
  providerId:string;
  formId:string;
  submitted:boolean;


  constructor(private qcs: AnswerControlService, 
    private dynamicFormervice: DynamicFormService,
    private activatedRoute: ActivatedRoute, 
    private location:Location,
    private router:Router) {  }

  ngOnInit() {
    this.loading=true;
    this.submitted=false;
    this.fileName="Seleccionar un archivo";
    this.nextDisable=true;
    this.backwardQuestions= new Array();
    this.currentQuestion=new Question('','',false);
    this.form= new FormGroup({});
    this.questionsList= new Map<string, Question>();
    this.activatedRoute.queryParams.subscribe((params: Params) => {
      this.userId=params['user'];
      this.providerId=params['provider'];
      this.formId=params['form'];
        this.dynamicFormervice.getTotalPages(this.formId).subscribe(total => {
          this.totalPage=total;
          this.actualPage=1;
          this.calculateAdvancePercentage();
            this.dynamicFormervice.getQuestions(this.formId).subscribe(items =>{
              items.map(item =>{
                this.questionsList.set(item.id,item);
              });
            this.dynamicFormervice.getInitialQuestionId(this.formId).subscribe(id =>{
              this.currentQuestion=this.questionsList.get(id);
              this.previousQuestion=this.currentQuestion;
              this.loading=false;
            });
          });
        });
    });
    this.answers$.subscribe(a=>{
      this.form = this.qcs.toFormGroup(a);
    });
  }

  onSubmit() {
    this.loading=true;
    this.payLoad=JSON.stringify(this.form.value);
    let quote:QuoteDto = new QuoteDto();
    quote.requirements= this.payLoad;
    this.dynamicFormervice.createQuote(quote,this.userId,this.providerId).subscribe((resp:HttpResponse<String>)=>{
      this.dynamicFormervice.uploadQuotationImage(this.photo,resp.body.substring(resp.body.indexOf(':')+1,resp.body.lastIndexOf('"'))).
      subscribe(resp => {
        this.submitted=true;
        this.loading=false;
      });
    });
  }

  calculateAdvancePercentage(){
    this.percentageCompletion=(this.actualPage/this.totalPage)*100;
  }

  next(validatios:string, childId:string){
    if(validatios!=null){
      let controlRestrictions:string[]=validatios.split(',');
      let validCount=0;
      for (let cr of controlRestrictions) {
        if(this.form.get(cr).status=='VALID'){
          validCount++;
        }
     }
     if(validCount==controlRestrictions.length){
       this.nextDisable=false;
       this.backwardQuestions.push(this.currentQuestion.id);
       this.currentQuestion=this.questionsList.get(childId);
       if(this.currentQuestion.last==true){
        this.percentageCompletion=100;
       }else{
        this.actualPage=(this.actualPage+1);
        this.calculateAdvancePercentage();
       }
     }
    }else{
      this.backwardQuestions.push(this.currentQuestion.id);
      this.currentQuestion=this.questionsList.get(childId);
      if(this.currentQuestion.last==true){
        this.percentageCompletion=100;
      }else{
       this.actualPage=(this.actualPage+1);
       this.calculateAdvancePercentage();
      }
    }
  }

  back(){
    if(this.actualPage==1){
      this.location.back();
    }
    else{
      let questionId:string = this.backwardQuestions.pop();
      this.currentQuestion=this.questionsList.get(questionId);
      this.actualPage=(this.actualPage-1);
      this.calculateAdvancePercentage();
    }
  }

  cancel(){
    this.navegateToProfessionForm();
  }

  navegateToProfessionForm(){
    switch (this.formId) {
      case 'ALB00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '1' } });
        break;
      case 'CAR00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '2' } });
        break;
      case 'DIS00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '3' } });
        break;
      case 'ELE00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '4' } });
        break;  
      case 'CLI00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '5' } });
        break;
      case 'FUM00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '6' } });
        break;
      case 'MAQ00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '7' } });
        break;
      case 'PIN00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '8' } });
        break;
      case 'PLO00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '9' } });
        break;
      case 'JAR00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '10' } });
        break;
      case 'SOL00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '11' } });
        break;
      case 'TAP00PR':
        this.router.navigate(['/expertos'], { queryParams: { profession: '12' } });
        break;
      default:
  }
  }

  onFileChange(event) {
    this.loading=true;
    if(event.target.files && event.target.files.length > 0) {
    let image:File = event.target.files[0];
    let reader=new FileReader();
    reader.readAsDataURL(image);
      reader.onload = (event:any) => {
        let base64=event.target.result
        let filename=image.name;
        let type=image.type;
        this.reduceQuality(base64,filename,type,(file)=>{
          this.photo = file;
          this.fileName=this.photo.name;
          this.loading=false;
        });
      }
    }
    else{
      this.loading=false;
    }
  }

  getBase64(file,callback) {
    var reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function() {
      let result = reader.result;
      callback(result);
    };
    reader.onerror = function (error) {
      console.log('Error: ', error);
    };
 }

  reduceQuality(base64Image,filename,extension,callback){
    let canvas = document.createElement('canvas');
    let ctx=canvas.getContext("2d");
    var image = new Image();
   image.onload = ()=>{
    var width = image.width,
            height = image.height,
        canvas = document.createElement('canvas'),
            ctx = canvas.getContext("2d");

    // set proper canvas dimensions before transform & export
        canvas.width = width;
      canvas.height = height;

        // draw image
    ctx.drawImage(image, 0, 0);

    let url:string = '';
    if(extension=='image/jpeg'){
      url = canvas.toDataURL('image/jpeg', 0.1);
    }
    else{
      url = canvas.toDataURL();
    }
    let file:File=this.dataURLtoFile(url,filename);
    callback(file);
  };
    image.src = base64Image;  
  }
  dataURLtoFile(dataurl, filename) {
    let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while(n--){
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new File([u8arr], filename, {type:mime});
}

}

package.json

{
  "name": "front-end",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^5.2.0",
    "@angular/common": "^5.2.0",
    "@angular/compiler": "^5.2.0",
    "@angular/core": "^5.2.0",
    "@angular/forms": "^5.2.0",
    "@angular/http": "^5.2.0",
    "@angular/platform-browser": "^5.2.0",
    "@angular/platform-browser-dynamic": "^5.2.0",
    "@angular/router": "^5.2.0",
    "angular2-text-mask": "^8.0.4",
    "angularfire2": "^5.0.0-rc.6",
    "bootstrap": "^4.1.0",
    "core-js": "^2.4.1",
    "firebase": "^4.12.1",
    "jquery": "^3.3.1",
    "moment": "^2.20.1",
    "mydatepicker": "^2.6.3",
    "ngx-loading": "^1.0.14",
    "offcanvas-bootstrap": "^2.5.2",
    "popper.js": "^1.14.3",
    "rxjs": "^5.5.6",
    "zone.js": "0.10.3"
  },
  "devDependencies": {
    "@angular/cli": "1.6.5",
    "@angular/compiler-cli": "^5.2.0",
    "@angular/language-service": "^5.2.0",
    "@types/jasmine": "~2.8.3",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "^4.0.1",
    "jasmine-core": "~2.8.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~2.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~4.1.0",
    "tslint": "~5.9.1",
    "typescript": "~2.5.3"
  }
}

angular-cli.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "front-end"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "styles-app-loading.scss",
        "styles.css",
        "../node_modules/font-awesome/css/font-awesome.css"
      ],
      "scripts": ["../node_modules/jquery/dist/jquery.slim.min.js",
        "../node_modules/popper.js/dist/umd/popper.min.js",
        "../node_modules/bootstrap/dist/js/bootstrap.min.js",
        "../node_modules/offcanvas-bootstrap/dist/js/bootstrap.offcanvas.min.js"],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  "e2e": {
    "protractor": {
      "config": "./protractor.conf.js"
    }
  },
  "lint": [
    {
      "project": "src/tsconfig.app.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "src/tsconfig.spec.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "e2e/tsconfig.e2e.json",
      "exclude": "**/node_modules/**"
    }
  ],
  "test": {
    "karma": {
      "config": "./karma.conf.js"
    }
  },
  "defaults": {
    "styleExt": "css",
    "component": {}
  }

}

The question is why my app only works properly when I don't use AOT feature? Why is this so strange?

What should I do to solve this problem and I can use the AOT feature, since I do not want my application to be penalized in performance? Any ideas?

Important: Before separating the application by modules, the application worked well in production. The described problem appeared after separating my application by modules, however this problematic component is part of the main module.

AlejoDev
  • 4,345
  • 9
  • 36
  • 67

2 Answers2

1

Try adding that flag to your ng build to see if it helps. (--build-optimizer=false)

Dicekey
  • 405
  • 4
  • 12
0

The error can be related to the specific rxjs version.

See https://github.com/telerik/kendo-angular/issues/1230

Seems like it is the same error with rxjs Observable.merge.apply() that appears only in aot builds.

Sh Svyatoslav
  • 359
  • 4
  • 12