私はAngular 5アプリをテストモードで実行するとき、つまりng serve
コマンドを使用してローカルで実行するときに問題なく動作します。ただし、デプロイするときにプロダクションモードのアプリケーション、つまりng build --prod
コマンドを使用すると、アプリケーションは問題なくエクスポートされますが、本番環境でアプリケーションを使用すると、この問題が発生します(これらはgoogleおよびサファリ)
Google Chrome Console:
Safariコンソール:
ただし、次のコマンド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機能を使用するにはどうすればよいですか?何か案は?
重要:アプリケーションをモジュールで分離する前に、アプリケーションは本番環境でうまく機能していました。上記の問題は、アプリケーションをモジュールで分離した後に発生しましたが、この問題のあるコンポーネントはメインモジュールの一部です。
Ngビルドにそのフラグを追加して、効果があるかどうかを確認してください。 (--build-optimizer = false)
エラーは、rxjsバージョンに関連している可能性があります。
https://github.com/telerik/kendo-angular/issues/12 を参照してください
Aotビルドでのみ、rxjs Observable.merge.apply()
でも同じエラーが発生するようです。