Error Property 'controls' does not exist on type 'AbstractControl'. while compiling for production - angular5

Hi I am getting an error while compiling my project . I am using angular 5 and typescript.
i am giving an npm run build and from project root folder and the following is the error i recieve.
bash-4.2$ npm run build
> primecast#5.0.1 build /tmp/tester001/mep-private/ui
> ng build --prod
10% building modules 3/4 modules 1 active ...ter001/mep-private/ui/src/styles.scssNode#moveTo was deprecated. Use Container#append.
Date: 2019-12-20T11:17:00.575Z
Hash: 175df3f370fd7976d3c4
Time: 16261ms
chunk {0} styles.bb4ae0c2b0dd75fdc7ee.bundle.css (styles) 159 kB [initial] [rendered]
chunk {1} polyfills.3bc34265385d52184eab.bundle.js (polyfills) 86 bytes [initial] [rendered]
chunk {2} main.e402deade8b026b7d50e.bundle.js (main) 84 bytes [initial] [rendered]
chunk {3} inline.9d07561b59257af76b95.bundle.js (inline) 1.45 kB [entry] [rendered]
ERROR in src/app/accounts/account-service-provider-clients/account-service-provider-clients.component.html(17,28): : Property 'controls' does not exist on type 'AbstractControl'.
the following is content of the account-service-provider-clients.component.html
<form novalidate [formGroup]="formGroup">
<div formArrayName="aliases">
<div [formGroupName]="i" *ngFor="let item of formGroup.get('aliases').controls; let i = index">
<div class="row">
<div class="col-2">
<input type="hidden" formControlName="id" >
<pc-form-field [errorMessages]="['required','maxlength']">
<input type="text" formControlName="name" [placeholder]="'PLACEHOLDERS.SERVICE_PROVIDER_CLIENT_NAME' | translate" name="client-name">
</pc-form-field>
</div>
<div class="col-2">
<pc-form-field [errorMessages]="['maxlength']">
<input type="text" formControlName="companyNumber" [placeholder]="'PLACEHOLDERS.SERVICE_PROVIDER_CLIENT_COMPANY_NUMBER' | translate" name="company-number">
</pc-form-field>
</div>
<div class="col-2">
<pc-button class="remove-btn" theme="default" icon="trash" kind="flat" (click)="removeItem(i)">
{{ 'ACTIONS.REMOVE' | translate }}
</pc-button>
</div>
<div class="col-*">
</div>
</div>
</div>
<div>
<pc-button theme="success" icon="check" type="submit" [disabled]="formGroup.invalid" (click)="handleSubmit()">
{{'ACTIONS.SUBMIT' | translate}}
</pc-button>
<pc-button theme="default" icon="times" kind="flat" [link]="cancelLink">
{{'ACTIONS.CANCEL' | translate}}
</pc-button>
</div>
</div>
</form>
The following is my account-service-provider-clients.component.html
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators, FormArray, FormControl } from '#angular/forms';
import { duplicateClientNameValidator, AuthenticationService, ToastService } from '../../shared';
import { ServiceProviderClient, Account} from '../../shared/models'
import { ServiceProviderClientBulkUpdateRequest } from '../../shared/models/requests'
import { ServiceProviderClientService } from '../../shared/services';
import { take, tap, switchMap } from 'rxjs/operators';
import { TranslateService } from '#ngx-translate/core';
#Component({
selector: 'pc-account-service-provider-clients',
templateUrl: './account-service-provider-clients.component.html',
styleUrls: ['./account-service-provider-clients.component.scss']
})
export class AccountServiceProviderClientsComponent implements OnInit {
formGroup: FormGroup;
isLoading = false;
account: Account;
constructor( private fb: FormBuilder, private serviceProviderClientService: ServiceProviderClientService, private authenticationService: AuthenticationService
, private translate: TranslateService, private toastService: ToastService) {}
ngOnInit() {
this.isLoading = true;
this.formGroup = this.fb.group({
aliases: this.fb.array([])
},{validator: [duplicateClientNameValidator('aliases')]});
this.authenticationService.currentUser$.pipe(
take(1),
tap( user => { this.account = user.account } ),
switchMap( () => this.serviceProviderClientService.getAccountServiceProviderClients())
).subscribe(
serviceProviderClients => {
this.populateForm(serviceProviderClients);
this.isLoading = false;
}
);
}
populateForm(serviceProviderClients: ServiceProviderClient[] ) {
while (this.aliases.length !== 0) {
this.aliases.removeAt(0);
}
for ( const serviceProviderClient of serviceProviderClients) {
const formGroupNew: FormGroup = new FormGroup( {
id: new FormControl( { value:serviceProviderClient.id, disabled:false}),
name: new FormControl({ value:serviceProviderClient.name, disabled:false},[Validators.required, Validators.maxLength(50)]),
companyNumber: new FormControl({ value:serviceProviderClient.companyNumber, disabled:false},Validators.maxLength(20))
}
);
this.aliases.push(formGroupNew);
}
}
get aliases() {
return this.formGroup.get('aliases') as FormArray;
}
addAlias() {
const formGroupNew: FormGroup = new FormGroup( {
id: new FormControl( { value:'', disabled:false}),
name: new FormControl({ value:'', disabled:false},[Validators.required, Validators.maxLength(50)]),
companyNumber: new FormControl({ value:'', disabled:false},Validators.maxLength(20))
}
);
this.aliases.push(formGroupNew);
}
removeItem( index :number){
this.aliases.removeAt(index);
}
handleSubmit() {
const formArrayLength = this.aliases.length;
const serviceProviderClientsArray: ServiceProviderClient[] = [];
let serviceProviderClientUpdateRequest : ServiceProviderClientBulkUpdateRequest;
for(let i = 0;i<formArrayLength;i++)
{
const itemFrmGroup:FormGroup = <FormGroup>this.aliases.at(i);
const serviceProviderClient: ServiceProviderClient = {
id: itemFrmGroup.get("id").value,
name: itemFrmGroup.get("name").value,
companyNumber: itemFrmGroup.get("companyNumber").value
}
serviceProviderClientsArray.push(serviceProviderClient);
}
serviceProviderClientUpdateRequest = {
clients : serviceProviderClientsArray
}
this.isLoading = true;
this.serviceProviderClientService.bulkUpdate(serviceProviderClientUpdateRequest).subscribe(
(result) => {
this.isLoading = false;
this.populateForm(result);
this.translate
.get([
'VALIDATION.SUCCESS.SERVICE_PROVIDER_CLIENT_SETTINGS.TITLE',
'VALIDATION.SUCCESS.SERVICE_PROVIDER_CLIENT_SETTINGS.BODY'
])
.subscribe(translate => {
this.toastService.toast({
title: translate['VALIDATION.SUCCESS.SERVICE_PROVIDER_CLIENT_SETTINGS.TITLE'],
body: translate['VALIDATION.SUCCESS.SERVICE_PROVIDER_CLIENT_SETTINGS.BODY'],
type: 'success',
icon: 'check-circle'
});
});
},
()=> {
this.isLoading = false;
}
);
}
get cancelLink(): string {
return '/dashboard';
}
}
I think the usage of form array is causing this problem.
many websites suggest the usage of get method for accessing the form array and i am doing it but no success
get aliases() {
return this.formGroup.get('aliases') as FormArray;
}
really appreciate any help
thank you so much
Prasanth

I have fixed the issue as follows
1) created a new method as below in my ts file
get aliasesArrayControl() {
return (this.formGroup.get('aliases') as FormArray).controls;
}
and then accessing it as follows in my control.
<div formArrayName="aliases" *ngFor="let item of aliasesArrayControl; let i = index">
</div>
thanks

Related

Dynamicly add a Button that is linked to an action

On connect, I want to add some markup that is connected to an action within the same controller. Is this possible? How is it done?
This is what I am trying so far in my tricksmods controller:
addEmbedButton() {
const buttonHTML = '<button type="button" class="trix-button" data-trix-attribute="embed" data-action="click->tricksmods#showembed" title="Embed" tabindex="-1">Embed</button>'
this.buttonGroupBlockTools.insertAdjacentHTML("beforeend", buttonHTML)
}
showembed(){
console.log('showembed')
}
The markup is added, but the showembed action is not fired on click.
I worked this out. I needed to use the toolbarElement of the trix editor to get at my button and dialog.
import { Controller } from "#hotwired/stimulus"
import Trix from 'trix'
import Rails from "#rails/ujs"
export default class extends Controller {
static get targets() {
return [ "field" ]
}
connect() {
this.addEmbedButton()
this.addEmbedDialog()
this.eventListenerForEmbedButton()
this.eventListenerForAddEmbedButton()
}
addEmbedButton() {
const buttonHTML = '<button type="button" class="trix-button tricks-embed" data-trix-attribute="embed" data-trix-action="embed" data-action="click->tricks#showembed" title="Embed" tabindex="-1">Embed</button>'
this.buttonGroupBlockTools.insertAdjacentHTML("beforeend", buttonHTML)
}
addEmbedDialog() {
const dialogHTML = `<div class="trix-dialog trix-dialog--link" data-trix-dialog="embed" data-trix-dialog-attribute="embed" data-tricks-target="embeddialog">
<div class="trix-dialog__link-fields">
<input type="text" name="embed" class="trix-input trix-input--dialog" placeholder="Paste your URL" aria-label="embed code" required="" data-trix-input="" disabled="disabled">
<div class="trix-button-group">
<input type="button" class="trix-button trix-button--dialog" data-trix-custom="add-embed" value="Add">
</div>
</div>
</div>`
this.dialogsElement.insertAdjacentHTML("beforeend", dialogHTML)
}
showembed(e){
console.log('showembed')
const dialog = this.toolbarElement.querySelector('[data-trix-dialog="embed"]')
const embedInput = this.dialogsElement.querySelector('[name="embed"]')
if (event.target.classList.contains("trix-active")) {
event.target.classList.remove("trix-active");
dialog.classList.remove("trix-active");
delete dialog.dataset.trixActive;
embedInput.setAttribute("disabled", "disabled");
} else {
event.target.classList.add("trix-active");
dialog.classList.add("trix-active");
dialog.dataset.trixActive = "";
embedInput.removeAttribute("disabled");
embedInput.focus();
}
}
eventListenerForEmbedButton() {
this.toolbarElement.querySelector('[data-trix-action="embed"]').addEventListener("click", e => {
this.showembed(e)
})
}
eventListenerForAddEmbedButton() {
this.dialogsElement.querySelector('[data-trix-custom="add-embed"]').addEventListener("click", event => {
console.log('embeddy')
const content = this.dialogsElement.querySelector("[name='embed']").value
if (content) {
let _this = this
let formData = new FormData()
formData.append("content", content)
Rails.ajax({
type: 'PATCH',
url: '/admin/embed.json',
data: formData,
success: ({content, sgid}) => {
const attachment = new Trix.Attachment({content, sgid})
_this.element.editor.insertAttachment(attachment)
_this.element.editor.insertLineBreak()
}
})
}
})
}
//////////////// UTILS ////////////////////////////////////////////////////
get buttonGroupBlockTools() {
return this.toolbarElement.querySelector("[data-trix-button-group=block-tools]")
}
get buttonGroupTextTools() {
return this.toolbarElement.querySelector("[data-trix-button-group=text-tools]")
}
get buttonGroupFileTools(){
return this.toolbarElement.querySelector("[data-trix-button-group=file-tools]")
}
get dialogsElement() {
return this.toolbarElement.querySelector("[data-trix-dialogs]")
}
get toolbarElement() {
return this.element.toolbarElement
}
}

Vue.js / How to access the function in another component

I just started learning Vue.js. I need to call the function in another component in my project. When I add new data to the table with createAnnouncement.vue, I want to go into announcement.vue and call the queryAnnouncement function. How can I do that? I would appreciate if you could help, please explain with a sample. Or edit my codes.
Announcement.Vue template:
<template>
// more div or not important template code
<div class="dataTables_filter" style="margin-bottom:10px">
<label>
<input class="form-control form-control-sm" placeholder="Search" aria-controls="m_table_1" type="search" v-model="searchField" #change="filter()">
</label>
<a style="float:right" href="#" data-target="#create-announcement-modal" data-toggle="modal" class="btn btn-primary">
<i class="">Add</i>
</a>
</div>
// more div or not important template code
</template>
Announcement.Vue Script Code:
<script>
import toastr from "toastr";
export default {
name: 'announcement',
data() {
return {
announcements: [],
searchField: "",
deleteCsrfToken: this.$root.csrfTokens["deleteAnnouncement"]
}
},
beforeMount: async function () {
await this.queryAnnouncements();
},
methods: {
filter: async function () {
await this.queryAnnouncements(this.searchField);
},
queryAnnouncements: async function (filter, pageSize, pageIndex, sortBy, sortType) {
var data = {
"query[general-filter]": filter,
"pagination[perpage]": !!pageSize ? pageSize : 10,
"pagination[page]": !!pageIndex ? pageIndex : 1,
"sort[field]": sortType,
"sort[sort]": !!sortBy ? sortBy : "asc"
};
let response = await axios
.get("/Announcement/QueryAnnouncements", { params: data })
this.announcements = response.data.data;
},
}
}
createAnnouncement.vue code:
<template>
<button #click="createAnnouncement()" v-bind:disabled="contentDetail === ''" class="btn btn-success">
Save</button>
//not important template codes
<template>
<script>
import toastr from "toastr";
export default {
name: 'create-announcement',
data() {
return {
contentDetail: "",
createCsrfToken: this.$root.csrfTokens["createAnnouncement"],
}
},
methods: {
createAnnouncement: async function () {
var self = this;
var data = {
content: this.contentDetail,
};
let response = await axios
.post("/Announcement/createAnnouncement",
data,
{
headers: {
RequestVerificationToken: self.createCsrfToken
}
})
if (response.status == 200) {
$("#create-announcement-modal .close").click()
$("#create-announcement-form").trigger('reset');
toastr["success"]("Kayıt başarıyla eklendi!", "Başarılı!");
self.contentDetail = "";
}
else {
toastr["warning"]("Hata", "Kayıt Eklenemedi.");
}
}
},
}
</script>
Please show with sample or arrangement, my english is not very good. Thanks.

Vue-Dropzone processQueue not working

On my website you can upload a dog with attributes and images.
Vuejs is the frontend and Laravel the backend.
I am using this vue-dropzone component in my project to upload images.
The problem
I want to upload the images and the attributes of a dog at the same time (when the user clicks the submit button), so that the image files can be linked to the dog's id in the database.
Laravel function to register a new dog (route: 'api/dogs')
public function store(Request $request)
{
$attributes = [
'name' => $request->input('name'),
'type' => $request->input('dogType'),
...
];
$dogId = Dog::insertGetId($attributes);
// Upload files
if ($request->hasFile('files')) {
// getting all files
$files = $request->file('files');
// Count files to be uploaded
$file_count = count($files);
// start count how many uploaded
$uploadcount = 0;
if($uploadcount == $file_count) {
return true;
} else {
FileController::store($request, 0, 0, $dogId, $files, $uploadcount);
}
}
return $dogId;
}
Dropzone component (Formdropzone)
<template>
<div>
<dropzone
:id="this.id"
:url="this.url"
:accepted-file-types='"image/*"'
:use-font-awesome="true"
:preview-template="template"
:auto-process-queue="false" <----
:upload-multiple="true"
:parallel-uploads=100
:max-files=100
#vdropzone-success="showSuccess"
>
</dropzone>
</div>
</template>
<script>
import Dropzone from 'vue2-dropzone'
export default {
props: {
id: {
type: String,
required: true
},
url: {
type: String,
required: true
}
},
components: {
Dropzone
},
methods: {
showSuccess(file) {
console.log('A file was successfully uploaded')
},
template() {
return `
<div class="dz-preview dz-file-preview">
<div class="dz-image" style="width: 200px;height: 200px">
<img data-dz-thumbnail /></div>
<div class="dz-details">
<div class="dz-size"><span data-dz-size></span></div>
<div class="dz-filename"><span data-dz-name></span></div>
</div>
<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
<div class="dz-error-message"><span data-dz-errormessage></span></div>
<div class="dz-success-mark"><i class="fa fa-check"></i></div>
<div class="dz-error-mark"><i class="fa fa-close"></i></div>
</div>
`;
}
}
}
</script>
Register dog component
<tab-content title="Images">
<div class="form__input__wrapper">
<span class="label">Images (optional)</span>
<formdropzone url="http://domain.local/api/dogs" ref="dogDropzone" id="dogDropzone"></formdropzone>
</div>
</tab-content>
<script>
import Formdropzone from './Formdropzone'
export default {
data() {
return {
dog:{
name: '',
dogType: '',
...
}
}
},
methods: {
publish() {
this.$http.post('api/dogs', this.dog)
.then(response => {
this.$refs.dogDropzone.processQueue() <----
this.$router.push('/feed')
})
}
},
components: {
'formdropzone': Formdropzone
}
</script>
The error message
Uncaught (in promise) TypeError: Cannot read property 'processQueue' of undefined
I would be very thankful for any kind of help!

Angular2 Service which create, show and manage it's inner Component? How to implement js alert()?

I tried to find a way for having and manage an angular2 Component in a Service but with no success:
I need to create:
AlertService{
alertConfirm(msg): Promise;
}
alertConfirm will prompt an Confirmation window with 2 buttons (Ok, Cancel) and will return users' choise as a Promise.
In General, the idea is to implement the famous JavaScript alert() method
but with a designed UI window and with also a cancel button.
The method will return a Promise with a response of user's choice: "OK" or "Cancel".
I tried to find a way for holding an "anonymous" component, AlertComponent, in AlertService:
AlertComponent{
showMsgConfirm(msg): Promise;
}
The Promise will be set with a response when user close prompt window or click "OK" or "Cancel".
The question:
How to make "AlertService" to have an inner "AlertComponent" which can be managed by it's "alertOK" method?
I mean, I didn't find a way for "alertConfirm" to call "showMsgConfirm" method and to return it's Promise as a response.
for example, calling from main app component:
this.alertService.alertConfirm("Save changes?").then(res => {
if(res.ok){console.log("Can be saved");
}, err=> { });
Any ideas for this?
Thanks,
Update:2 different ideas for solution, but with no sucess to manage the AlertComponent:
import { Injectable, ViewContainerRef, ReflectiveInjector, ComponentFactoryResolver, ComponentRef } from '#angular/core';
import { AlertComponent } from './../components/modales/AlertComponent/AlertComponent.component';
#Injectable()
export class AlertService {
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
public createAlertComp(vCref: ViewContainerRef): ComponentRef<any> {
let factory = this.componentFactoryResolver.resolveComponentFactory(AlertComponent);
/*
//Option 1:
// vCref is needed cause of that injector..
let injector = ReflectiveInjector.fromResolvedProviders([], vCref.parentInjector);
// create component without adding it directly to the DOM
let comp = factory.create(injector);
// add inputs first !! otherwise component/template crashes ..
comp.instance.model = modelInput;
// all inputs set? add it to the DOM ..
vCref.insert(comp.hostView);
return comp;
*/
//Option 2:
var componentRef: ComponentRef<AlertComponent> = vCref.createComponent(factory);
return null;
}
}
And the answer is... :
The Service:
_counter is used for each modal to have a unique name.
comp.instance.close is a property of inner component for subscribing for EventEmitter.
.
import { Injectable, ViewContainerRef, ReflectiveInjector, ComponentFactoryResolver, ComponentRef, EventEmitter } from '#angular/core';
import { CtmAlertComponent } from './ctmAlert/ctmAlert.component';
#Injectable()
export class AlertCtmService {
private _vcr: ViewContainerRef;
private _counter: number = 0;
constructor(private componentFactoryResolver: ComponentFactoryResolver, public viewRef: ViewContainerRef) {
console.log("AlertCtmService.constructor:");
//TODO: Consider appending to this.viewRef: "#alertCtmServiceContainer" as a Dom elemnt perent container which will hold all AlertModals:
// Maybe by:
// this.viewRef.element.nativeElement.insertAdjacentHTML('beforeend', '<div class="alertCtmServiceContainer"></div>');
this._vcr = this.viewRef;
}
public alertOK(alertMsg: string): EventEmitter<any> {
return this.createEventEmitterComponent("CtmAlertComponent", alertMsg, false);
}
public alertConfirm(alertMsg: string): EventEmitter<any> {
return this.createEventEmitterComponent("CtmAlertComponent", alertMsg, true);
}
private createEventEmitterComponent(componentName: string, alertMsg: string, isConfirm: boolean): EventEmitter<any> {
console.log("AlertCtmService.createEventEmitterComponent:");
switch (componentName) {
case "CtmAlertComponent":
default:
var _component = CtmAlertComponent;
break;
}
let factory = this.componentFactoryResolver.resolveComponentFactory(_component);
// vCref is needed cause of that injector..
let injector = ReflectiveInjector.fromResolvedProviders([], this._vcr.parentInjector);
// create component without adding it directly to the DOM
let comp = factory.create(injector);
// add inputs first !! otherwise component/template crashes ..
comp.instance.close.subscribe(resp => {
console.log("AlertCtmService.createEventEmitterComponent: comp.instance.close.subscribe: resp=" + resp.ok);
comp.destroy();
})
comp.instance.alertBodyMsg = alertMsg;
comp.instance.isConfirm = isConfirm;
comp.instance.nameId = "Modal" +(++this._counter).toString();
// all inputs set? add it to the DOM ..
this._vcr.insert(comp.hostView);
//return null;
return comp.instance.close;
}
public init(vCref: ViewContainerRef): ViewContainerRef {
this._vcr = vCref;
return this._vcr;
}
}
Inner Component:
Using Bootstrap for handling display of Modal in UI: modal('show') \ modal('hide').
.
import { Component, AfterViewInit, Input, ViewChild, ElementRef, Renderer, NgZone, EventEmitter} from '#angular/core';
#Component({
selector: 'ctm-alert',
styles: [``],
templateUrl: '/app/shared/alertCtm/ctmAlert/CtmAlert.component.html',
styleUrls: ['./app/shared/alertCtm/ctmAlert/CtmAlert.component.css'],
providers: []
})
export class CtmAlertComponent implements AfterViewInit {
public ModalIsVisible: boolean;
//private static subscriptions: Object = {};
//enums = Enums;
close = new EventEmitter();
public nameId = "";
private isOk = false;
alertBodyMsg: string = "";
isConfirm = false;
constructor() {
console.log("CtmAlertComponent.constructor:");
}
ngAfterViewInit() {
this.showModal();
var attrId = this.getIdAttr();
$('#' + attrId).on('hidden.bs.modal', function () {
debugger;
console.log('CtmAlertComponent: #licenseModal_XXX.on(hidden.bs.modal)');
this.submitStatus();
}.bind(this) );
}
showModal() {
this.ModalIsVisible = true;
var attrId = '#' +this.getIdAttr();
$(attrId).modal('show');
}
hideModal() {
this.ModalIsVisible = false;
var attrId = '#' + this.getIdAttr();
$(attrId).modal('hide');
}
getIdAttr(): string {
return "ctmAlertModal_" + this.nameId;
}
submitStatus() {
var resp = { ok: (this.isOk == true) };
this.close.emit(resp);
}
submitOk() {
this.isOk = true;
this.hideModal();
}
submitCancel() {
this.isOk = false;
this.hideModal();
}
}
App's Declaration:
unfortunately, we must declare the anonymus component in our main-app module.
We must add a declaration of entryComponents: [CtmAlertComponent],
.
import { CtmAlertComponent } from './shared/alertCtm/ctmAlert/ctmAlert.component';
#NgModule({
imports: [
BrowserModule,
HttpModule,
AppRoutingModule,
...
],
declarations: [
CtmAlertComponent,
AppComponent,
...
],
entryComponents: [CtmAlertComponent],
providers: [
...
],
bootstrap: [AppComponent],
})
export class AppModule { }
enableProdMode();
Modal UI:
this html template is based on bootstrap's UI:
.
<div class="ctmAlertModal modal fade in" [id]="getIdAttr()" role="dialog">
<div class="modal-dialog modal-lg" [ngClass]="{'modal-lg-6': true }">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header" style="">
<div class="pull-right" style="position: relative;">
<span class="fa fa-times-circle" aria-hidden="true" style="color: #949494"></span>
</div>
</div>
<div class="modal-body">
<div class="modal-body-msg">
{{alertBodyMsg}}
</div>
<div class="modal-body-buttons">
<div style="margin: 0 auto;" [style.width]="(isConfirm)? '165px' : '70px' ">
<button type="button" *ngIf="isConfirm" class="btn-submit pull-left btn-cancel" [ngClass]="{'disabled': false }" [disabled]="false" (click)="submitCancel()">
<!--<img alt="End-Training" class="centering-me2" src="../../../contents/training_state_stop_white.svg">-->
Cancel
</button>
<button type="button" class="btn-submit pull-right" [ngClass]="{'disabled': false }" [disabled]="false" (click)="submitOk()">
<!--<img alt="Resume-Training" src="../../../contents/training_state_play_white.svg">-->
OK
</button>
</div>
</div>
</div>
</div>
</div>
</div>
.
Usage::
for example:
.
this.alertCtmService.alertOK("Save changes???").subscribe(function (resp) {
console.log("alertCtmService.alertOK.subscribe: resp=" + resp.ok);
this.saveData();
}.bind(this) );
**
An example I built : https://plnkr.co/qc1ZM6
**
sources:
building-angular-2-components-on-the-fly-a-dialog-box-example
angular2-ngmodule

how to unittest a vuejs component update with methods using promise

I'm testing a Vue component with Jest. This is my working directory :
Here is my component Subscription.vue :
<template>
<div id="page">
<page-title icon="fa-list-alt">
<translate slot="title">Subscription</translate>
<translate slot="comment">Consult the detail of your subscription</translate>
</page-title>
<panel v-if="error">
<span slot="title">
<icon icon="fa-exclamation-triangle"></icon>
<translate>Error</translate>
</span>
{{ error }}
</panel>
<panel v-else-if="subscription_dict">
<span slot="title">{{ _subscription_address }}</span>
<div class="row" v-if="subscription_dict.product.hsi">
<div class="col-xs-12">
<subscription-hsi-panel
:hsi="subscription_dict.product.hsi"
:business="context.business">
</subscription-hsi-panel>
</div>
</div>
<div class="row" v-if="subscription_dict.product.itv">
<div class="col-xs-12">
<subscription-itv-panel
:itv="subscription_dict.product.itv">
</subscription-itv-panel>
</div>
</div>
<div class="row" v-if="subscription_dict.product.voip">
<div class="col-xs-6">
<panel icon="icon-voip">
<translate slot="title">Phone</translate>
telefon products
</panel>
</div>
</div>
</panel>
<div v-else class="text-center">
<i class="fa fa-spinner fa-pulse fa-3x fa-fw"></i>
</div>
<span v-if="subscription_dict"><b>subscription_dict.product.voip : </b>{{ subscription_dict.product.voip }}</br></span>
<span v-if="subscription_dict"><b>subscription_dict.product : </b>{{ subscription_dict.product }}</br></span>
</div>
</template>
<script>
import PageTitle from '../../core/components/PageTitle.vue'
import SubscriptionHsiPanel from './SubscriptionHsiPanel.vue'
import SubscriptionItvPanel from './SubscriptionItvPanel.vue'
import Panel from '../../core/components/Panel.vue'
import Icon from '../../core/components/Icon.vue'
import api from '../../core/api.js'
export default {
data() {
return {
subscription_dict: false,
error: false
}
},
props: ['requests_url', 'context'],
computed: {
_subscription_address() {
var sub_address = this.subscription_dict.subscription_address
return sub_address + ' - ' + this.subscription_dict.package.join(' - ')
}
},
created() {
this.get_subscription()
this.translate()
},
methods: {
get_subscription() {
let self = this
api.get_subscription(self.requests_url.subscriptions_request)
.then(function(response) {
self.subscription_dict = response
})
.catch(function(error) {
if(error) {
self.error = error
} else {
self.error = self.$gettext(
'We were not able to retrieve your subscription information!')
}
})
},
translate() {
this.$gettext('Bridge hsi')
this.$gettext('Bridge voip_biz')
this.$gettext('Router')
}
},
components: {
PageTitle,
Panel,
Icon,
SubscriptionHsiPanel,
SubscriptionItvPanel
}
}
</script>
<style lang="sass">
#import "../../core/css/tooltip"
.table
table-layout: fixed
> tbody > tr >
th
width: 33%
td
vertical-align: middle
</style>
And here is my test subscription.js:
import Vue from 'vue'
import translations from 'src/translations.json'
import GetTextPlugin from 'vue-gettext'
import VTooltip from 'v-tooltip'
import Subscription from 'src/subscription/components/Subscription'
jest.mock('../../core/api');
Vue.use(GetTextPlugin, {
availableLanguages: {
en: 'English',
fr: 'Français',
de: 'Deutsch',
},
defaultLanguage: 'fr',
translations: translations
})
Vue.use(VTooltip)
it('render when error', (done) => {
const renderer = require('vue-server-renderer').createRenderer()
const vm = new Vue({
el: document.createElement('div'),
render: h => h(Subscription, {
props: {
requests_url: {
subscriptions_request: 'error_empty_promise'
}
}
})
})
renderer.renderToString(vm, (err, str) => {
setImmediate(() => {
expect(str).toMatchSnapshot()
done()
})
})
})
The component's method get_subscription() use my API function get_subscription:
import axios from 'axios'
export default {
get_subscription(url) {
return axios.get(url, {
credentials: 'same-origin'
})
.then(function(response){
if(response.data.error){
return Promise.reject(response.data.error)
}else{
return Promise.resolve(response.data)
}
})
.catch(function(error) {
return Promise.reject(false)
})
}
}
For my test, I have mocked this function like this :
const promises_object = {
error_empty_promise: new Promise((resolve, reject) => {
process.nextTick(
() => reject(false)
)
})
}
export default {
get_subscription(url) {
return promises_object[url]
}
}
Now, in the test I render and compare against a snapshot. My issue is, I can't find a way to wait that the promise of get_subscription is reject before making the comparison.
The result is that my snapshot reflect the state of the component before the update of the DOM, which is done after the asynchronous call on the API.
Is there a way to tell jest to wait until the Promise is reject before calling expect(str).toMatchSnapshot() ?
Jest docs say
it expects the return value to be a Promise that is going to be
resolved.
You have a promise that is going to be rejected, so you need a Promise that resolves when your promise is rejected.
isRejected(rejectingPromise, someExpects) {
return new Promise((resolve) => {
rejectingPromise.then(
() => null, // if it resolves, fail
(err) => { // if it rejects, resolve
// Maybe do someExpects() here
resolve();
}
});
}