Commit c8eefdbc authored by Konstantin Schulz's avatar Konstantin Schulz

project configuration is now loaded more securely, i.e. using RxJS

parent 00df96d4
{ {
"name": "mc_frontend", "name": "mc_frontend",
"version": "1.5.0", "version": "1.5.1",
"author": "Ionic Framework", "author": "Ionic Framework",
"homepage": "https://ionicframework.com/", "homepage": "https://ionicframework.com/",
"scripts": { "scripts": {
......
...@@ -79,15 +79,13 @@ export class CorpusService { ...@@ -79,15 +79,13 @@ export class CorpusService {
this.helperService.initApplicationState(); this.helperService.initApplicationState();
this.initCurrentCorpus(); this.initCurrentCorpus();
this.initCurrentTextRange(); this.initCurrentTextRange();
HelperService.waitForConfig().then(() => { this.checkForUpdates().finally(() => {
this.checkForUpdates().finally(() => { this.checkAnnisResponse().then(() => {
this.checkAnnisResponse().then(() => { this.restoreLastCorpus().then(() => {
this.restoreLastCorpus().then(() => {
this.isMostRecentSetupLoaded = true;
});
}, () => {
this.isMostRecentSetupLoaded = true; this.isMostRecentSetupLoaded = true;
}); });
}, () => {
this.isMostRecentSetupLoaded = true;
}); });
}); });
this.initPhenomenonMap(); this.initPhenomenonMap();
...@@ -147,12 +145,15 @@ export class CorpusService { ...@@ -147,12 +145,15 @@ export class CorpusService {
checkForUpdates(): Promise<void> { checkForUpdates(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// check local storage for necessary updates HelperService.config.pipe(take(1)).subscribe((config: object) => {
const updateInfoJSON: object = JSON.parse(window.localStorage.getItem(HelperService.config['localStorageKeyUpdateInfo'])); // check local storage for necessary updates
this.getCorpora(updateInfoJSON ? new Date(updateInfoJSON['corpora'].lastAccessTime).getTime() : 0).then(() => { const updateInfoJSON: object = JSON.parse(window.localStorage.getItem(config['localStorageKeyUpdateInfo']));
return resolve(); this.getCorpora(updateInfoJSON ? new Date(updateInfoJSON['corpora'].lastAccessTime).getTime() : 0)
}, () => { .then(() => {
return reject(); return resolve();
}, () => {
return reject();
});
}); });
}); });
} }
...@@ -161,54 +162,60 @@ export class CorpusService { ...@@ -161,54 +162,60 @@ export class CorpusService {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.availableCorpora = []; this.availableCorpora = [];
this.availableAuthors = []; this.availableAuthors = [];
// get corpora from REST API HelperService.config.pipe(take(1)).subscribe((config: object) => {
const url = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiCorporaPath']; // get corpora from REST API
const params: HttpParams = new HttpParams().set('last_update_time', lastUpdateTimeMS.toString()); const url: string = config['backendBaseUrl'] + config['backendApiCorporaPath'];
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((data: object) => { const params: HttpParams = new HttpParams().set('last_update_time', lastUpdateTimeMS.toString());
if (data) { HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((data: object) => {
const corpusList: CorpusMC[] = data['corpora'] as CorpusMC[]; if (data) {
window.localStorage.setItem(HelperService.config['localStorageKeyCorpora'], JSON.stringify(corpusList)); const corpusList: CorpusMC[] = data['corpora'] as CorpusMC[];
const updateInfo: object = {corpora: {lastAccessTime: new Date().getTime()}}; window.localStorage.setItem(config['localStorageKeyCorpora'], JSON.stringify(corpusList));
window.localStorage.setItem(HelperService.config['localStorageKeyUpdateInfo'], JSON.stringify(updateInfo)); const updateInfo: object = {corpora: {lastAccessTime: new Date().getTime()}};
this.processCorpora(corpusList); window.localStorage.setItem(config['localStorageKeyUpdateInfo'], JSON.stringify(updateInfo));
return resolve(); this.processCorpora(corpusList);
} else { return resolve();
} else {
this.loadCorporaFromLocalStorage();
return resolve();
}
}, async (error: HttpErrorResponse) => {
this.loadCorporaFromLocalStorage(); this.loadCorporaFromLocalStorage();
return resolve(); const toast = await this.toastCtrl.create({
} message: this.corporaUnavailableString,
}, async (error: HttpErrorResponse) => { duration: 3000,
this.loadCorporaFromLocalStorage(); position: 'top'
const toast = await this.toastCtrl.create({ });
message: this.corporaUnavailableString, toast.present().then();
duration: 3000, return reject(error);
position: 'top'
}); });
toast.present().then();
return reject(error);
}); });
}); });
} }
getCTStextPassage(urn: string): Promise<AnnisResponse> { getCTStextPassage(urn: string): Promise<AnnisResponse> {
return new Promise(((resolve, reject) => { return new Promise(((resolve, reject) => {
const url = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiRawtextPath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
const params: HttpParams = new HttpParams().set('urn', urn); const url: string = config['backendBaseUrl'] + config['backendApiRawtextPath'];
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((ar: AnnisResponse) => { const params: HttpParams = new HttpParams().set('urn', urn);
return resolve(ar); HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((ar: AnnisResponse) => {
}, (error: HttpErrorResponse) => { return resolve(ar);
return reject(error); }, (error: HttpErrorResponse) => {
return reject(error);
});
}); });
})); }));
} }
getCTSvalidReff(urn: string): Promise<string[]> { getCTSvalidReff(urn: string): Promise<string[]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const fullUrl: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiValidReffPath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
const params: HttpParams = new HttpParams().set('urn', urn); const fullUrl: string = config['backendBaseUrl'] + config['backendApiValidReffPath'];
HelperService.makeGetRequest(this.http, this.toastCtrl, fullUrl, params).then((reff: string[]) => { const params: HttpParams = new HttpParams().set('urn', urn);
resolve(reff); HelperService.makeGetRequest(this.http, this.toastCtrl, fullUrl, params).then((reff: string[]) => {
}, (error: HttpErrorResponse) => { resolve(reff);
reject(error); }, (error: HttpErrorResponse) => {
reject(error);
});
}); });
}); });
} }
...@@ -218,18 +225,20 @@ export class CorpusService { ...@@ -218,18 +225,20 @@ export class CorpusService {
if (this.annisResponse.frequency_analysis.length) { if (this.annisResponse.frequency_analysis.length) {
return resolve(); return resolve();
} else { } else {
const url: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiFrequencyPath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
const params: HttpParams = new HttpParams().set('urn', this.currentUrn); const url: string = config['backendBaseUrl'] + config['backendApiFrequencyPath'];
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((fis: FrequencyItem[]) => { const params: HttpParams = new HttpParams().set('urn', this.currentUrn);
HelperService.isLoading = false; HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((fis: FrequencyItem[]) => {
this.annisResponse.frequency_analysis = fis; HelperService.isLoading = false;
HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => { this.annisResponse.frequency_analysis = fis;
as.mostRecentSetup.annisResponse = this.annisResponse; HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => {
this.helperService.saveApplicationState(as).then(); as.mostRecentSetup.annisResponse = this.annisResponse;
this.helperService.saveApplicationState(as).then();
});
return resolve();
}, () => {
return reject();
}); });
return resolve();
}, () => {
return reject();
}); });
} }
}); });
...@@ -336,11 +345,13 @@ export class CorpusService { ...@@ -336,11 +345,13 @@ export class CorpusService {
} }
public loadCorporaFromLocalStorage() { public loadCorporaFromLocalStorage() {
const storedCorporaJSONstring: string = window.localStorage.getItem(HelperService.config['localStorageKeyCorpora']); HelperService.config.pipe(take(1)).subscribe((config: object) => {
if (storedCorporaJSONstring) { const storedCorporaJSONstring: string = window.localStorage.getItem(HelperService.config['localStorageKeyCorpora']);
const corpusList: CorpusMC[] = JSON.parse(storedCorporaJSONstring) as CorpusMC[]; if (storedCorporaJSONstring) {
this.processCorpora(corpusList); const corpusList: CorpusMC[] = JSON.parse(storedCorporaJSONstring) as CorpusMC[];
} this.processCorpora(corpusList);
}
});
} }
processAnnisResponse(ar: AnnisResponse, saveToCache: boolean = true) { processAnnisResponse(ar: AnnisResponse, saveToCache: boolean = true) {
......
...@@ -16,6 +16,7 @@ import {TranslateService} from '@ngx-translate/core'; ...@@ -16,6 +16,7 @@ import {TranslateService} from '@ngx-translate/core';
import {AnnisResponse} from 'src/app/models/annisResponse'; import {AnnisResponse} from 'src/app/models/annisResponse';
import {CorpusService} from 'src/app/corpus.service'; import {CorpusService} from 'src/app/corpus.service';
import {VocabularyService} from 'src/app/vocabulary.service'; import {VocabularyService} from 'src/app/vocabulary.service';
import {take} from 'rxjs/operators';
@Component({ @Component({
selector: 'app-exercise-list', selector: 'app-exercise-list',
...@@ -82,24 +83,26 @@ export class ExerciseListPage implements OnInit { ...@@ -82,24 +83,26 @@ export class ExerciseListPage implements OnInit {
} }
getExerciseList(): void { getExerciseList(): void {
const url: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiExerciseListPath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
this.hasVocChanged = false; const url: string = config['backendBaseUrl'] + config['backendApiExerciseListPath'];
let params: HttpParams = new HttpParams().set('lang', this.translateService.currentLang); this.hasVocChanged = false;
if (this.vocService.currentReferenceVocabulary) { let params: HttpParams = new HttpParams().set('lang', this.translateService.currentLang);
params = params.set('vocabulary', VocabularyCorpus[this.vocService.currentReferenceVocabulary]); if (this.vocService.currentReferenceVocabulary) {
params = params.set('frequency_upper_bound', this.vocService.frequencyUpperBound.toString()); params = params.set('vocabulary', VocabularyCorpus[this.vocService.currentReferenceVocabulary]);
} params = params.set('frequency_upper_bound', this.vocService.frequencyUpperBound.toString());
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((exercises: ExerciseMC[]) => { }
this.availableExercises = exercises; HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((exercises: ExerciseMC[]) => {
this.exercises = exercises; this.availableExercises = exercises;
this.exercises.forEach((exercise: ExerciseMC) => { this.exercises = exercises;
this.translateService.get(ExerciseTypeTranslation[MoodleExerciseType[exercise.exercise_type]]).subscribe( this.exercises.forEach((exercise: ExerciseMC) => {
value => exercise.exercise_type_translation = value); this.translateService.get(ExerciseTypeTranslation[MoodleExerciseType[exercise.exercise_type]]).subscribe(
this.metadata[exercise.eid] = [exercise.work_author, exercise.exercise_type_translation, exercise.work_title] value => exercise.exercise_type_translation = value);
.join(' ').toLowerCase(); this.metadata[exercise.eid] = [exercise.work_author, exercise.exercise_type_translation, exercise.work_title]
.join(' ').toLowerCase();
});
this.filterExercises(this.currentSearchValue);
}, () => {
}); });
this.filterExercises(this.currentSearchValue);
}, () => {
}); });
} }
...@@ -113,16 +116,18 @@ export class ExerciseListPage implements OnInit { ...@@ -113,16 +116,18 @@ export class ExerciseListPage implements OnInit {
} }
showExercise(exercise: ExerciseMC) { showExercise(exercise: ExerciseMC) {
const url: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiExercisePath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
const params: HttpParams = new HttpParams().set('eid', exercise.eid); const url: string = config['backendBaseUrl'] + config['backendApiExercisePath'];
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((ar: AnnisResponse) => { const params: HttpParams = new HttpParams().set('eid', exercise.eid);
// save this exercise only locally in the CorpusService (not as MostRecentSetup in the HelperService) because HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((ar: AnnisResponse) => {
// users just want to have a quick look at it // save this exercise only locally in the CorpusService (not as MostRecentSetup in the HelperService) because
this.corpusService.annisResponse = ar; // users just want to have a quick look at it
const met: MoodleExerciseType = MoodleExerciseType[exercise.exercise_type]; this.corpusService.annisResponse = ar;
this.corpusService.exercise.type = ExerciseType[met.toString()]; const met: MoodleExerciseType = MoodleExerciseType[exercise.exercise_type];
HelperService.goToPreviewPage(this.navCtrl).then(); this.corpusService.exercise.type = ExerciseType[met.toString()];
}, () => { HelperService.goToPreviewPage(this.navCtrl).then();
}, () => {
});
}); });
} }
......
...@@ -54,25 +54,27 @@ export class ExerciseParametersPage implements OnInit { ...@@ -54,25 +54,27 @@ export class ExerciseParametersPage implements OnInit {
async generateExercise() { async generateExercise() {
const phenomenon: Phenomenon = this.corpusService.exercise.queryItems[0].phenomenon; const phenomenon: Phenomenon = this.corpusService.exercise.queryItems[0].phenomenon;
if (0 < HelperService.config['maxTextLength'] && HelperService.config['maxTextLength'] < this.corpusService.currentText.length) { HelperService.config.pipe(take(1)).subscribe(async (config: object) => {
const toast = await this.toastCtrl.create({ if (0 < config['maxTextLength'] && config['maxTextLength'] < this.corpusService.currentText.length) {
message: this.textTooLongString, const toast = await this.toastCtrl.create({
duration: 3000, message: this.textTooLongString,
position: 'top' duration: 3000,
}); position: 'top'
toast.present().then(); });
} else if ((phenomenon === Phenomenon.lemma && !this.corpusService.exercise.queryItems[0].values) || toast.present().then();
this.corpusService.exercise.type === ExerciseType.matching && !this.corpusService.exercise.queryItems[1].values[0]) { } else if ((phenomenon === Phenomenon.lemma && !this.corpusService.exercise.queryItems[0].values) ||
const toast = await this.toastCtrl.create({ this.corpusService.exercise.type === ExerciseType.matching && !this.corpusService.exercise.queryItems[1].values[0]) {
message: this.emptyQueryValueString, const toast = await this.toastCtrl.create({
duration: 3000, message: this.emptyQueryValueString,
position: 'top' duration: 3000,
}); position: 'top'
toast.present().then(); });
} else { toast.present().then();
this.corpusService.annisResponse.solutions = null; } else {
this.getExerciseData(); this.corpusService.annisResponse.solutions = null;
} this.getExerciseData();
}
});
} }
public getDisplayValue(query: QueryMC, key: string, queryIndex: number = 0): string { public getDisplayValue(query: QueryMC, key: string, queryIndex: number = 0): string {
...@@ -130,56 +132,62 @@ export class ExerciseParametersPage implements OnInit { ...@@ -130,56 +132,62 @@ export class ExerciseParametersPage implements OnInit {
} }
getH5Pexercise(formData: FormData) { getH5Pexercise(formData: FormData) {
const url = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiExercisePath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
HelperService.currentError = null; const url = config['backendBaseUrl'] + config['backendApiExercisePath'];
HelperService.isLoading = true; HelperService.currentError = null;
this.http.post(url, formData).subscribe((ar: AnnisResponse) => { HelperService.isLoading = true;
HelperService.isLoading = false; this.http.post(url, formData).subscribe((ar: AnnisResponse) => {
// save the old frequency analysis in case we want to change the exercise parameters at a later time HelperService.isLoading = false;
ar.frequency_analysis = this.corpusService.annisResponse.frequency_analysis; // save the old frequency analysis in case we want to change the exercise parameters at a later time
HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => { ar.frequency_analysis = this.corpusService.annisResponse.frequency_analysis;
as.mostRecentSetup.annisResponse = ar; HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => {
this.helperService.saveApplicationState(as).then(); as.mostRecentSetup.annisResponse = ar;
this.corpusService.annisResponse.exercise_id = ar.exercise_id; this.helperService.saveApplicationState(as).then();
this.corpusService.annisResponse.uri = ar.uri; this.corpusService.annisResponse.exercise_id = ar.exercise_id;
this.corpusService.annisResponse.solutions = ar.solutions; this.corpusService.annisResponse.uri = ar.uri;
HelperService.goToPreviewPage(this.navCtrl).then(); this.corpusService.annisResponse.solutions = ar.solutions;
HelperService.goToPreviewPage(this.navCtrl).then();
});
}, async (error: HttpErrorResponse) => {
HelperService.isLoading = false;
HelperService.currentError = error;
const toast = await this.toastCtrl.create({
message: HelperService.generalErrorAlertMessage,
duration: 3000,
position: 'top'
});
toast.present().then();
}); });
}, async (error: HttpErrorResponse) => {
HelperService.isLoading = false;
HelperService.currentError = error;
const toast = await this.toastCtrl.create({
message: HelperService.generalErrorAlertMessage,
duration: 3000,
position: 'top'
});
toast.present().then();
}); });
} }
getKwicExercise(formData: FormData) { getKwicExercise(formData: FormData) {
const kwicUrl: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiKwicPath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
HelperService.currentError = null; const kwicUrl: string = config['backendBaseUrl'] + config['backendApiKwicPath'];
HelperService.isLoading = true; HelperService.currentError = null;
this.http.post(kwicUrl, formData).subscribe((svgString: string) => { HelperService.isLoading = true;
HelperService.isLoading = false; this.http.post(kwicUrl, formData).subscribe((svgString: string) => {
this.exerciseService.kwicGraphs = svgString; HelperService.isLoading = false;
this.navCtrl.navigateForward('kwic').then(); this.exerciseService.kwicGraphs = svgString;
}, async (error: HttpErrorResponse) => { this.navCtrl.navigateForward('kwic').then();
HelperService.isLoading = false; }, async (error: HttpErrorResponse) => {
HelperService.currentError = error; HelperService.isLoading = false;
const toast = await this.toastCtrl.create({ HelperService.currentError = error;
message: HelperService.generalErrorAlertMessage, const toast = await this.toastCtrl.create({
duration: 3000, message: HelperService.generalErrorAlertMessage,
position: 'top' duration: 3000,
position: 'top'
});
toast.present().then();
}); });
toast.present().then();
}); });
} }
initTranslation() { initTranslation() {
this.translateService.get('TEXT_TOO_LONG').subscribe( HelperService.config.pipe(take(1)).subscribe((config: object) => {
value => this.textTooLongString = value + HelperService.config['maxTextLength']); this.translateService.get('TEXT_TOO_LONG').subscribe(
value => this.textTooLongString = value + config['maxTextLength']);
});
this.translateService.get('QUERY_VALUE_EMPTY').subscribe(value => this.emptyQueryValueString = value); this.translateService.get('QUERY_VALUE_EMPTY').subscribe(value => this.emptyQueryValueString = value);
} }
......
...@@ -36,36 +36,38 @@ export class ExercisePage implements OnInit { ...@@ -36,36 +36,38 @@ export class ExercisePage implements OnInit {
loadExercise(): void { loadExercise(): void {
this.activatedRoute.queryParams.subscribe((params: object) => { this.activatedRoute.queryParams.subscribe((params: object) => {
if (params['eid']) { HelperService.config.pipe(take(1)).subscribe((config: object) => {
let url: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiExercisePath']; if (params['eid']) {
const httpParams: HttpParams = new HttpParams().set('eid', params['eid']); let url: string = config['backendBaseUrl'] + config['backendApiExercisePath'];
HelperService.makeGetRequest(this.http, this.toastCtrl, url, httpParams).then((ar: AnnisResponse) => { const httpParams: HttpParams = new HttpParams().set('eid', params['eid']);
HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => { HelperService.makeGetRequest(this.http, this.toastCtrl, url, httpParams).then((ar: AnnisResponse) => {
as.mostRecentSetup.annisResponse = ar; HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => {
this.helperService.saveApplicationState(as).then(); as.mostRecentSetup.annisResponse = ar;
this.corpusService.annisResponse = ar; this.helperService.saveApplicationState(as).then();
const met: MoodleExerciseType = MoodleExerciseType[ar.exercise_type]; this.corpusService.annisResponse = ar;
this.corpusService.exercise.type = ExerciseType[met.toString()]; const met: MoodleExerciseType = MoodleExerciseType[ar.exercise_type];
// this will be called via GET request from the h5p standalone javascript library this.corpusService.exercise.type = ExerciseType[met.toString()];
url = `${HelperService.config['backendBaseUrl']}${HelperService.config['backendApiH5pPath']}` + // this will be called via GET request from the h5p standalone javascript library
`?eid=${this.corpusService.annisResponse.exercise_id}&lang=${this.translateService.currentLang}`; url = `${config['backendBaseUrl']}${config['backendApiH5pPath']}` +
window.localStorage.setItem(HelperService.config['localStorageKeyH5P'], url); `?eid=${this.corpusService.annisResponse.exercise_id}&lang=${this.translateService.currentLang}`;
const exerciseTypePath: string = this.corpusService.exercise.type === ExerciseType.markWords ? window.localStorage.setItem(config['localStorageKeyH5P'], url);
'mark_words' : 'drag_text'; const exerciseTypePath: string = this.corpusService.exercise.type === ExerciseType.markWords ?
this.exerciseService.initH5P(exerciseTypePath); 'mark_words' : 'drag_text';
this.exerciseService.initH5P(exerciseTypePath);
});
}, () => {
}); });
}, () => { } else {
}); const exerciseType: string = params['type'];
} else { const exerciseTypePath: string = exerciseType === this.exerciseService.vocListString ?
const exerciseType: string = params['type']; this.exerciseService.fillBlanksString : exerciseType;
const exerciseTypePath: string = exerciseType === this.exerciseService.vocListString ? const file: string = params['file'];
this.exerciseService.fillBlanksString : exerciseType; const lang: string = this.translateService.currentLang;
const file: string = params['file']; window.localStorage.setItem(config['localStorageKeyH5P'],
const lang: string = this.translateService.currentLang; HelperService.baseUrl + '/assets/h5p/' + exerciseType + '/content/' + file + '_' + lang + '.json');
window.localStorage.setItem(HelperService.config['localStorageKeyH5P'], this.exerciseService.initH5P(exerciseTypePath);
HelperService.baseUrl + '/assets/h5p/' + exerciseType + '/content/' + file + '_' + lang + '.json'); }
this.exerciseService.initH5P(exerciseTypePath); });
}
}); });
} }
......
...@@ -10,6 +10,7 @@ import {Storage} from '@ionic/storage'; ...@@ -10,6 +10,7 @@ import {Storage} from '@ionic/storage';
import {Language} from 'src/app/models/language'; import {Language} from 'src/app/models/language';
import {ReplaySubject} from 'rxjs'; import {ReplaySubject} from 'rxjs';
import {TextData} from './models/textData'; import {TextData} from './models/textData';
import {take} from 'rxjs/operators';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
...@@ -29,7 +30,7 @@ export class HelperService { ...@@ -29,7 +30,7 @@ export class HelperService {
Voc: CaseValue.vocative