我在Angular 4应用程序中使用了一个模态对话框,该对话框利用可观察到的BehaviorSubject,并且在许多其他领域都可以正常工作,但不适用于一个特定用例。
有关某些打字稿的示例,请参见下文。
dialog.service.ts:
import { Injectable, TemplateRef } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Dialog, DialogSizeEnum } from './dialog.model';
@Injectable()
export class DialogService {
private d: Dialog = { template: null, size: DialogSizeEnum.XLarge };
private dialogSubject = new BehaviorSubject<Dialog>({ template: null, size: DialogSizeEnum.XLarge });
constructor() { }
showDialog(template: TemplateRef<any>, size = DialogSizeEnum.XLarge, requiresAction = false) {
Object.assign(this.d, { template: template, size: size, requiresAction: requiresAction });
if (this.d !== null) {
this.dialogSubject.next(this.d);
}
}
getDialog(): BehaviorSubject<Dialog> {
return this.dialogSubject;
}
clear() {
this.dialogSubject.next(null);
}
}
dialog.component.ts:
import { Component, OnInit, OnDestroy, ViewEncapsulation, ViewChild } from '@angular/core';
import { Dialog, DialogSizeEnum } from './dialog.model';
import { DialogService } from './dialog.service';
import { PlatformLocation } from '@angular/common';
import { Router, NavigationStart, RouterEvent, NavigationEnd } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
import { state, trigger, transition, animate, style, AnimationEvent } from '@angular/animations';
@Component({
selector: 'app-dialog',
templateUrl: './dialog.component.html',
styles: [],
encapsulation: ViewEncapsulation.None,
animations: [
trigger('dialogState', [
state('void', style(
{
opacity: 0,
'margin-top': '-300px'
}
)),
transition('* => *', [
animate('0.5s ease')
])
]),
trigger('dialogBgState', [
state('void', style(
{
opacity: 0,
}
)),
transition('* => *', [
animate('0.5s ease')
])
])
]
})
export class DialogComponent implements OnInit, OnDestroy {
@ViewChild('engModal') engModal: any;
routerSubscription: Subscription;
subscription: Subscription;
dialog: Dialog;
showDialog: boolean;
dialogSize = DialogSizeEnum;
constructor(
private _location: PlatformLocation,
private _dialog: DialogService,
private _router: Router) { }
open() {
this.showDialog = true;
const body = document.body;
body.classList.add('cell-modal-open');
}
close(validClose: boolean) {
if (validClose === true) {
this.dialog = undefined;
}
}
dialogAnimDone(event: AnimationEvent) {
if (event.toState === 'void') {
const body = document.body;
body.classList.remove('cell-modal-open');
this.showDialog = false;
}
}
getDialogSize(dialogSize: DialogSizeEnum): string {
switch (dialogSize) {
case DialogSizeEnum.Standard:
return '';
case DialogSizeEnum.Large:
return 'modal-lg';
case DialogSizeEnum.XLarge:
return 'modal-xl';
case DialogSizeEnum.Small:
return 'modal-sm';
default:
return '';
}
}
private handleDialog(d: Dialog) {
if (!d) {
this.close(true);
} else if (d.template) {
if (this.showDialog) {
this.close(true);
}
this.dialog = d;
this.open();
}
}
private handleDialogError(err: any) {
console.log('Dialog Component error: ' + err);
}
private initialiseRoutingEventListeners(): void {
this._location.onPopState(() => {
if (this.showDialog) {
this.close(true);
}
});
this.routerSubscription = this._router.events.subscribe((event: RouterEvent) => {
if (event instanceof NavigationStart) {
if (this.showDialog) {
this.close(true);
}
}
if (event instanceof NavigationEnd) {
if (this.showDialog) {
this.close(true);
}
}
});
}
ngOnInit() {
this.subscription = this
._dialog
.getDialog()
.subscribe({
next: (d) => { this.handleDialog(d) },
error: (err) => this.handleDialogError(err)
});
this.initialiseRoutingEventListeners();
}
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
if (this.routerSubscription) {
this.routerSubscription.unsubscribe();
}
}
}
requests.module.ts(模态正常工作的业务区域模块):
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import {
UserComponent, DetailComponent, DetailModalComponent, CreateComponent, CreateAmendmentComponent,
ReviewAmendmentListComponent, ViewAmendmentListComponent, ListComponent,
ReviewedAmendmentListComponent, AssociatedComponent
} from './';
import { RequestsRoutingModule } from './requests-routing.module';
import { RequestService, JiraService, JiraCreateIssueComponent, RequestFilterComponent } from './shared';
import { RaciMembersComponent } from './shared/components/raci-members/raci-members.component';
import { CommentsModule } from './comments/comments.module';
import { TitleCasePipe } from '../shared';
@NgModule({
imports: [
RequestsRoutingModule,
SharedModule,
NgbModule,
CommentsModule
],
declarations: [
UserComponent,
DetailComponent,
CreateComponent,
CreateAmendmentComponent,
ReviewAmendmentListComponent,
ViewAmendmentListComponent,
ListComponent,
ReviewedAmendmentListComponent,
AssociatedComponent,
JiraCreateIssueComponent,
RaciMembersComponent,
RequestFilterComponent,
DetailModalComponent
],
providers: [
RequestService,
JiraService,
TitleCasePipe
],
exports: [
RequestFilterComponent,
DetailModalComponent
]
})
export class RequestsModule { }
detail-modal.component.ts(用于实际详细信息视图):
import { Component, OnInit, TemplateRef, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
import { DialogService } from '../../../shared/components/dialog/dialog.service';
import { FormBuilder, FormGroup, Validators, FormArray, FormControl, AbstractControl, ValidatorFn } from '@angular/forms';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import * as moment from 'moment';
import { Observable } from 'rxjs/Observable';
import { appsettings } from '../../../../settings/appsettings';
import {
RequestService, RequestEdit,
RequestAmendmentView, ViewAmendmentListComponent,
RequestEditAdminSubmit, RequestEditDevSubmit,
RequestEditCustomerMemberSubmit,
RagstatusEnum
} from '../../shared';
import {
RequestStatus, RequestType, AlertService, ActiveDirectory,
ActiveDirectoryService, UserSearchComponent, ConfirmComponent, TitleCasePipe,
ValueCompareValidator, ValueCompare
} from '../../../shared';
import { RequestQueue } from '../../../shared/models';
import { RequestQueueService } from 'app/request-queue/request-queue.service';
import { RequestUrgencyType, RequestUrgencyTypeEnum } from '../../../shared/models/request-urgency.model';
import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
import { DateCompareValidator } from 'app/shared/validation/value-compare/date-compare.function';
import { ActiveDirectoryUserValidator } from 'app/shared/validation/active-directory/active-directory.function';
import { RequestStatusEnum } from 'app/shared/models';
import { DateCompareValueValidator } from 'app/shared/validation/value-compare/date-compare-value.function';
import { RequiredOnDropDownValidator } from 'app/shared/validation/required-on-drop-down.function';
import Dialogmodel = require('../../../shared/components/dialog/dialog.model');
import DialogSizeEnum = Dialogmodel.DialogSizeEnum;
declare var $: any;
@Component({
selector: 'app-detail-modal',
templateUrl: './detail-modal.component.html',
styleUrls: ['../detail.component.scss']
})
export class DetailModalComponent implements OnInit {
@ViewChild('detailModal') detailModal: TemplateRef<any>;
@ViewChild('tagEntryBox') tagInput: ElementRef;
form: FormGroup;
id: number;
request: RequestEdit;
isBusy = true;
waitCursorMessage = 'Loading modal...';
hideAmendments = true; // is the amendment accordion hidden?
showAddAmendment = false; // is the create amendment visible?
amendmentAccordionOpen = false; // is the amendment accordian currently open?
disableControls = true;
showAmendButton = false;
membersUpdated = false;
shouldDisableResponsibleMembers = false;
amendmentsUpdated = true;
amendmentsFormGroup: FormGroup;
createAmendmentFormGroup: FormGroup;
showFooter = true;
descriptionMax: number;
showDateNeeded = false;
tempDate = new Date(moment('2001-01-01', 'YYYY-MM-DD').format('MM/DD/YYYY')).toDateString();
ragStatus = RagstatusEnum;
currentRate = 2;
hovered = 0;
showCompletedDate = false;
allQueues: RequestQueue[];
initialQueue: RequestQueue;
queueId: number;
modalEl = null;
constructor(
private _dialog: DialogService,
private _formBuilder: FormBuilder,
private _requestService: RequestService,
private _route: ActivatedRoute,
private _alertService: AlertService,
private _activeDirectoryService: ActiveDirectoryService,
private _location: Location,
private _sanitizer: DomSanitizer,
private _titleCase: TitleCasePipe,
private _queueService: RequestQueueService,
private _rootNode: ElementRef) { }
ngOnInit(): void { }
populateForm(): void {
this.getAllQueues();
this.getRequest(); }
getAllQueues(): void {
this.isBusy = true;
this._queueService.getAllQueues().subscribe({
next: (result) => {
this.allQueues = result;
},
error: (err) => {
this.isBusy = false;
this._alertService.sendAlert('warning', 'Unable to load queues!');
},
complete: () => {
this.isBusy = false;
}
});
}
getRequest(): void {
this._route.params
.switchMap(x => this._requestService.getDetailRequest(this.id))
.subscribe(request => this.receiveRequest(request), error => this.loadError());
}
loadError() {
this.isBusy = false;
this._alertService.sendAlert('warning', 'Failed to load request!');
}
showSave() {
return !this.shouldDisableControlForAdmin()
|| !this.shouldDisableControlForDev()
|| !this.shouldDisableControlForRequester()
|| this.membersUpdated
}
initForm() {
…
… (other code not relevant to this question)
…
}
setformValues() {
this.form.patchValue({
…
… (other code not relevant to this question)
…
});
}
private mapToNgbDate(val: any): NgbDateStruct {
if (!val) {
return null;
}
const d: Date = new Date(val);
return {
day: d.getDate(),
month: d.getMonth() + 1,
year: d.getFullYear()
};
}
private mapToDate(d: NgbDateStruct): Date {
if (!d) {
return null;
}
return new Date(d.year, d.month - 1, d.day);
}
onSubmit(): void {
this.isBusy = true;
this.waitCursorMessage = 'Saving...';
if (this.checkCanChangeStatus()) {
this.prepareSaveRequest();
} else {
this.isBusy = false;
this._alertService.sendAlert('info', 'You cannot change status when there is an outstanding amendment!');
}
}
submissionComplete(request) {
this.request = request;
this._alertService.sendAlert('success', 'Request updated!');
this.form.markAsPristine();
this.isBusy = false;
this.membersUpdated = false;
this.allowedToAddResponsibleMembers();
this.setShowAmendButton();
}
submissionError() {
this._alertService.sendAlert('warning', 'Failed to update request!');
this.isBusy = false;
}
prepareSaveRequest(): void {
…
… (other code not relevant to this question)
…
this.cleanSaveRequest();
}
…
… (other code not relevant to this question)
…
}
showDialog(requestId: number) {
this.id = requestId;
this.populateForm();
this._dialog.showDialog(this.detailModal, DialogSizeEnum.XLarge);
}
close() {
this._dialog.clear();
}
}
associated.component.ts(成功使用模式的示例):
import { Component, OnInit, ViewChild } from '@angular/core';
import { AdminListRequestViewModel, RequestService, RequestFilter, RagstatusEnum } from '../shared';
import { AlertService, RequestStatusEnum } from '../../shared';
import { DetailModalComponent } from '../detail/detail-modal/detail-modal.component'
@Component({
templateUrl: './associated.component.html',
styleUrls: ['./associated.component.scss']
})
export class AssociatedComponent implements OnInit {
@ViewChild('associatedDetailModalComponent') associatedDetailModalComponent: DetailModalComponent;
requests: AdminListRequestViewModel[];
isBusy = true;
selectedStatuses = [
RequestStatusEnum.New,
RequestStatusEnum.AwaitingApproval,
RequestStatusEnum.BeingTested,
RequestStatusEnum.InDesign,
RequestStatusEnum.InDevelopment,
RequestStatusEnum.Approved
];
selectedFilter: RequestFilter;
ragStatus = RagstatusEnum;
constructor(
private _requestService: RequestService,
private _alertService: AlertService) { }
ngOnInit(): void {
this.getRequests();
}
getRequests(): void {
this._requestService.getAssociatedRequests().subscribe(requests => this.receiveRequests(requests), error => this.onError());
}
onError() {
this._alertService.sendAlert('warning', 'Unable to load requests!');
this.isBusy = false;
}
receiveRequests(returnedRequests): void {
this.requests = returnedRequests;
this.isBusy = false;
}
filterChanged(newFilterValue: RequestFilter) {
this.selectedFilter = newFilterValue;
}
viewAssociatedRequest(requestId: number) {
this.associatedDetailModalComponent.showDialog(requestId);
}
}
manage-request-queues.component.ts(其中不显示模式):
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { RequestQueueService } from 'app/request-queue/request-queue.service';
import {
RequestQueue, RequestQueueRequest, RequestQueuePosition, RequestQueueChanges,
RequestQueueWithRequests, RequestQueueMove
} from 'app/request-queue/models';
import { RequestService } from 'app/requests/shared';
import { AlertService } from 'app/shared';
import { DragulaService } from 'ng2-dragula';
import { YesNoCancelComponent, RequestStatusEnum } from 'app/shared';
import { DetailModalComponent } from '../../requests/detail/detail-modal/detail-modal.component';
@Component({
selector: 'app-manage-request-queues',
templateUrl: './manage-request-queues.component.html',
styleUrls: ['./manage-request-queues.component.scss']
})
export class ManageRequestQueuesComponent implements OnInit, OnDestroy {
@ViewChild('manageRequestDetailModalComponent') manageRequestDetailModalComponent: DetailModalComponent;
selectedQueueId = '-1';
allQueues: RequestQueueWithRequests[];
dragulaDropSubscription: any;
dragulaDragSubscription: any;
dragulaDragEndSubscription: any;
moves = new Array<RequestQueueMove>();
saving = false;
isBusy = true;
waitCursorMessage = 'Loading...';
get canDeactivate(): boolean {
return this.moves.length === 0;
}
constructor(
private _queueService: RequestQueueService,
private _alertService: AlertService,
private _dragulaService: DragulaService) {
}
saveQueues() {
this.saving = true;
this.isBusy = true;
this.waitCursorMessage = 'Saving...';
const saveRequestCollection = this.buildSaveRequestCollection();
if (saveRequestCollection.length > 0) {
this._queueService.saveRequestQueueChanges(saveRequestCollection).subscribe({
next: (result) => {
this._alertService.sendAlert('success', 'Request queues have been updated.');
},
error: (err) => {
console.log(err);
const result = JSON.parse(err.error);
this._alertService.sendAlert('danger', result.messageList[0]);
this.saving = false;
this.isBusy = false;
},
complete: () => {
this.saving = false;
this.isBusy = false;
this.moves = new Array<RequestQueueMove>();
}
});
}
}
resetQueues() {
this.selectedQueueId = '-1';
this.allQueues = new Array<RequestQueueWithRequests>();
this.moves = new Array<RequestQueueMove>();
this.getAllQueuesWithRequests();
}
showQueue(requestQueueID: number) {
return +this.selectedQueueId === requestQueueID;
}
private buildSaveRequestCollection() {
const saveRequestCollection = new Array<RequestQueuePosition>();
this.allQueues.forEach(queue => queue.requests.forEach(request => {
const queueIndex = queue.requests.indexOf(request);
// set queue id to pick up user changes
request.requestQueueID = queue.requestQueueID;
if (this.checkHasUpdated(request, queue.requests.indexOf(request))) {
// must update position as this is likely to have changed
request.requestQueuePosition = queueIndex + 1;
saveRequestCollection.push({
requestID: request.requestID,
requestQueueID: request.requestQueueID,
queuePosition: request.requestQueuePosition,
requestStatusID: request.requestStatus.requestStatusId
});
}
}));
return saveRequestCollection;
}
ngOnInit() {
this.getAllQueuesWithRequests();
}
ngOnDestroy() {
this.selectedQueueId = '-1';
this.allQueues = new Array<RequestQueueWithRequests>();
this.moves = new Array<RequestQueueMove>();
if (this.dragulaDropSubscription) {
this.dragulaDropSubscription.unsubscribe();
}
if (this.dragulaDragSubscription) {
this.dragulaDragSubscription.unsubscribe();
}
if (this.dragulaDragEndSubscription) {
this.dragulaDragEndSubscription.unsubscribe();
}
}
viewManageRequest(requestId: number) {
this.manageRequestDetailModalComponent.showDialog(requestId);
}
}
request-queue.module.ts(由失败的组件使用):
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RequestQueueRoutingModule } from './request-queue-routing.module';
import { RequestQueueService } from './request-queue.service';
import { ManageRequestQueuesComponent } from './manage-request-queues/manage-request-queues.component';
import { ModifyRequestQueueComponent } from './modify-request-queue/modify-request-queue.component';
import { AddRequestQueueComponent } from './add-request-queue/add-request-queue.component';
import { SharedModule } from 'app/shared/shared.module';
import { DragulaModule } from 'ng2-dragula';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { RequestsModule } from '../requests/requests.module'
@NgModule({
imports: [
CommonModule,
SharedModule,
FormsModule,
DragulaModule,
NgbModule,
ReactiveFormsModule,
RequestQueueRoutingModule,
RequestsModule
],
declarations: [ManageRequestQueuesComponent, ModifyRequestQueueComponent, AddRequestQueueComponent],
providers: [RequestQueueService]
})
export class RequestQueueModule { }
我认为问题在于,dialog.service.ts中的dialogSubject(BehaviorSubject)在模式不起作用的情况下没有任何管理请求队列的观察者。这在detail-modal.component.ts中也很明显,其中注入的_dialog DialogService dialogSubject也没有观察者(该对话框的所有其他成功实现在其中)。
与成功的管理请求队列不同的另一个主要区别是,它位于应用程序的另一个区域,因此具有自己的模块(请参见上文)。我已经导入了RequestsModule,但这没有什么区别。我还尝试将DialogService添加到app.module中的提供程序,但无济于事。
我怀疑这可能是一个简单的修复程序,与不成功的组件没有订阅嵌入式BehaviorSubject有关,但是我不确定在其他用例中如何成功实现。
我意识到上面有很多代码,但是感兴趣的主要项目与dialog.service有关(我已经删除了大部分不相关的代码)。
我是Angular的新手,所以如果答案很明显,我深表歉意。
答案 0 :(得分:0)
经过多次试验和错误,导致问题的原因是DialogService由于在解决方案中的位置而未在manage-request-queues.component的范围内。 解决方法是将导入和提供程序声明从shared.module移到主app.module。