web-dev-qa-db-ja.com

Angular 5 AOTコンパイルに失敗しました

私はAngular 5アプリをテストモードで実行するとき、つまりng serveコマンドを使用してローカルで実行するときに問題なく動作します。ただし、デプロイするときにプロダクションモードのアプリケーション、つまりng build --prodコマンドを使用すると、アプリケーションは問題なくエクスポートされますが、本番環境でアプリケーションを使用すると、この問題が発生します(これらはgoogleおよびサファリ)

Google Chrome Console:

enter image description here

Safariコンソール:

enter image description here

ただし、次のコマンドng build --prod --aot falseを使用してアプリケーションをエクスポートすると、アプリケーションは問題なく動作します。

次に、コメント付きエラーに関連するコンポーネントを示します。

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": {}
  }

}

問題は、AOT機能を使用しない場合にアプリが正しく機能するのはなぜですか?なぜこんなに奇妙なのですか?

アプリケーションのパフォーマンスにペナルティを課したくないので、この問題を解決してAOT機能を使用するにはどうすればよいですか?何か案は?

重要:アプリケーションをモジュールで分離する前に、アプリケーションは本番環境でうまく機能していました。上記の問題は、アプリケーションをモジュールで分離した後に発生しましたが、この問題のあるコンポーネントはメインモジュールの一部です。

4
AlejoDev

Ngビルドにそのフラグを追加して、効果があるかどうかを確認してください。 (--build-optimizer = false)

1
Issam

エラーは、rxjsバージョンに関連している可能性があります。

https://github.com/telerik/kendo-angular/issues/12 を参照してください

Aotビルドでのみ、rxjs Observable.merge.apply()でも同じエラーが発生するようです。

0
Sh Svyatoslav