I am trying to make full calendar working in my Angular8 application. I tried few different ways of implementation but all let me down so far.
Most tutorials and examples suggests to use "jquery approach" but I think it is outdated, when I tried it, something like this:
$("#calendar").fullCalendar(
this.defaultConfigurations
);
I have an runtime error that fullCalendar is not a function.
The only way I could make calendar to work is this approach:
export class EventCalendarViewComponent extends BaseClass implements OnInit {
#ViewChild('calendar', null) calendar: FullCalendarComponent;
calendarPlugins = [dayGridPlugin];
calendarEvents = [];
addDialogRef: MatDialogRef<AddEventComponent>;
editDialogRef: MatDialogRef<EditEventComponent>;
constructor(private dialog: MatDialog, protected snackBar: MatSnackBar, private eventService: EventService) {
super(snackBar);
}
protected getData() {
this.eventService.getAllEvents().subscribe(res => {
res.forEach(event => {
const calendarEvent: CalendarEvent = this.schoolEventToCalendarEvent(event);
this.calendarEvents.push(calendarEvent);
});
});
}
ngOnInit(): void {
this.getData();
}
private schoolEventToCalendarEvent(event: SchoolEvent): CalendarEvent {
return {
id: event.id,
title: event.title,
start: moment(event.startDate).format('YYYY-MM-DD'),
end: moment(event.endDate).format('YYYY-MM-DD')
}
} ...
and html looks like this then:
<full-calendar #calendar
defaultView="dayGridMonth"
[plugins]="calendarPlugins"
[events]="calendarEvents"
[editable]="true"
[allDayDefault]="true"
[timeZone]="'local'"
></full-calendar>
However events populates with getData() method do not show on the calendar. The only way to see any events is static population of its variable:
calendarEvents = [{id: 1, title: 'static entry', start: '2019-09-05'}];
What is the trick here? I cannot find the way to refresh the calendar after db call is comleted.
Well the trick is (at least in my case) to declare the component like this:
<full-calendar #calendar
defaultView="dayGridMonth"
deepChangeDetection="true"
[plugins]="calendarPlugins"
[events]="calendarEvents"
[refetchResourcesOnNavigate]="true"
[editable]="true"
[allDayDefault]="true"
[timeZone]="'local'"
></full-calendar>
So I think that
deepChangeDetection="true"
is the the property which make the difference.
refetchResourcesOnNavigate which is there as well do not change anything. Since my data comes from the the database another property:
rerenderDelay
might be useful here, especially that deepChangeDetection might affect the performance.
Related
I would like to ensure that, even when it screws up, the event tracking code in my front end never causes an error that impacts the rest of the application. The event tracking is all handled by a few custom classes that deal with labeling evenings and adding the correct properties:
export class TaskTrackerService {
constructor(private abstractTracker: TrackerService) { }
// ------
created(task: Task): void {
this. abstractTracker.track('Task created', this.taskProps(task))
}
// ------
edited(task: Task): void {
this.abstractTracker.track('Task edited', this.taskProps(task))
}
// ------
private taskProps(task: Task): EventProperties{
return {
id: task.id,
task_title_char_count: task.title.length,
task_duration_seconds: task.duration,
completed: task.done,
created_at: new Date(task.created_at)
}
}
}
This class contains logic which could cause errors (e.g. task.created_at is null in the code above) and I want to ensure I catch these before they cause damage.
I want to neatly firewall this code
I want to ensure that any errors that are triggered in the tracking class don't propagate anywhere else in the app.
The only way I can think to do this is to add error handling for each individual tracking method as shown below:
export class TaskTrackerService {
constructor(private abstractTracker: TrackerService) { }
// ------
created(task: Task): void {
try{
this.abstractTracker.track('Task created', this.taskProps(task))
} catch(error){
this.abstractTracker.trackError('Error occurred')
}
}
...
}
Is there any way to wrap all instance-methods in a single, custom error handler?
To reduce the amount of boilerplate code, I'd like some means to ensure that all of the instanc methods bubble their errors into a class-level handler. Something like this:
export class TaskTrackerService {
constructor(private abstractTracker: TrackerService) { }
// I know this isn't possible but...
catchAll(error){
this.abstractTracker.trackError('Error occurred')
}
// ------
created(task: Task): void {
this.abstractTracker.track('Task created', this.taskProps(task))
}
...
}
Is anything like this possible?
For example I am on route /cars?type=coupe and I want to navigate to the same endpoint with additional query params (but keeping existing one). I am trying something like this
<a [routerLink]="['/cars']" [queryParams]="{model: 'renault'}" preserveQueryParams>Click</a>
The initial query params are preserved (type=cars) but added ones (model=renault) are ignored. Is this expected/correct behavior or is some kind of bug? Looks like preserveQueryParams has priority over queryParams? Is there any other smooth solution?
In Angular 4+, preserveQueryParams has been deprecated in favor of queryParamsHandling. The options are either 'merge' or 'preserve'.
In-code example (used in NavigationExtras):
this.router.navigate(['somewhere'], { queryParamsHandling: "preserve" });
In-template example:
<a [routerLink]="['somewhere']" queryParamsHandling="merge">
It just works this way unfortunately:
const q = preserveQueryParams ? this.currentUrlTree.queryParams : queryParams;
You could try to add custom directive like this:
#Directive({selector: 'a[routerLink][merge]'})
export class RouterLinkDirective implements OnInit
{
#Input()
queryParams: {[k: string]: any};
#Input()
preserveQueryParams: boolean;
constructor(private link:RouterLinkWithHref, private route: ActivatedRoute )
{
}
public ngOnInit():void
{
this.link.queryParams = Object.assign(Object.assign({}, this.route.snapshot.queryParams), this.link.queryParams);
console.debug(this.link.queryParams);
}
}
<a [routerLink]="['/cars']" merge [queryParams]="{model: 'renault'}">Click</a>
Update: See DarkNeuron answer below.
There is an open issue and also already a pull request to make preserveQueryParams a router setting instead of a per navigation setting
https://github.com/angular/angular/issues/12664
https://github.com/angular/angular/pull/12979
https://github.com/angular/angular/issues/13806
This can be handled using relativeTo and queryParamsHandling properties.
Try like this:
constructor(private _route: ActivatedRoute, private _router: Router)
this._router.navigate(
[],
{
relativeTo: this._route,
queryParams: { model: 'renault' },
queryParamsHandling: 'merge'
});
I have Ionic 2 app with one view for 3 different data sets. Data are loaded in constructor and based on variable in page params, it's decided which data set to show.
At every successful data call by observable, event handler logs success when data are loaded. But this only works when I click/load view for a first time. If I click for 2nd or any other time, data are not re-loaded (no log). Also, when I just console log anything, it won't show at 2nd+ click.
So I wonder what should I change to load data everytime and how constructor works in this manner.
This is how my code looks like. Jsons are called from namesListProvider.
#Component({
templateUrl: '...',
})
export class ListOfNames {
...
private dataListAll: Array<any> = [];
private dataListFavourites: Array<any> = [];
private dataListDisliked: Array<any> = [];
constructor(private nav: NavController, ...) {
...
this.loadJsons();
console.log('whatever');
}
loadJsons(){
this.namesListProvider.getJsons()
.subscribe(
(data:any) => {
this.dataListFavourites = data[0],
this.dataListDisliked = data[1],
this.dataListAll = data[2]
if (this.actualList === 'mainList') {
this.listOfNames = this.dataListAll;
this.swipeLeftList = this.dataListDisliked;
this.swipeRightList = this.dataListFavourites;
}
else if (...) {
...
}
this.listSearchResults = this.listOfNames;
}, err => console.log('hey, error when loading names list - ' + err),
() => console.info('loading Jsons complete')
)
}
What you're looking for are the Lifecycle events from Ionic2 pages. So instead of using ngOnInit you can use some of the events that Ionic2 exposes:
Page Event Description
---------- -----------
ionViewLoaded Runs when the page has loaded. This event only happens once per page being created and added to the DOM. If a page leaves but is cached, then this event will not fire again on a subsequent viewing. The ionViewLoaded event is good place to put your setup code for the page.
ionViewWillEnter Runs when the page is about to enter and become the active page.
ionViewDidEnter Runs when the page has fully entered and is now the active page. This event will fire, whether it was the first load or a cached page.
ionViewWillLeave Runs when the page is about to leave and no longer be the active page.
ionViewDidLeave Runs when the page has finished leaving and is no longer the active page.
ionViewWillUnload Runs when the page is about to be destroyed and have its elements removed.
ionViewDidUnload Runs after the page has been destroyed and its elements have been removed.
In your case, you can use the ionViewWillEnter page event like this:
ionViewWillEnter {
// This will be executed every time the page is shown ...
this.loadJsons();
// ...
}
EDIT
If you're going to obtain the data to show in that page asynchronously, since you don't know how long would it take until the data is ready, I'd recommend you to use a loading popup so the user can we aware of something happening in the background (instead of showing a blank page for a few seconds until the data is loaded). You can easily add that behaviour to your code like this:
// Import the LoadingController
import { LoadingController, ...} from 'ionic/angular';
#Component({
templateUrl: '...',
})
export class ListOfNames {
...
private dataListAll: Array<any> = [];
private dataListFavourites: Array<any> = [];
private dataListDisliked: Array<any> = [];
// Create a property to be able to create it and dismiss it from different methods of the class
private loading: any;
constructor(private loadingCtrl: LoadingController, private nav: NavController, ...) {
...
this.loadJsons();
console.log('whatever');
}
ionViewWillEnter {
// This will be executed every time the page is shown ...
// Create the loading popup
this.loading = this.loadingCtrl.create({
content: 'Loading...'
});
// Show the popup
this.loading.present();
// Get the data
this.loadJsons();
// ...
}
loadJsons(){
this.namesListProvider.getJsons()
.subscribe(
(data:any) => {
this.dataListFavourites = data[0],
this.dataListDisliked = data[1],
this.dataListAll = data[2]
if (this.actualList === 'mainList') {
this.listOfNames = this.dataListAll;
this.swipeLeftList = this.dataListDisliked;
this.swipeRightList = this.dataListFavourites;
}
else if (...) {
...
}
this.listSearchResults = this.listOfNames;
}, err => console.log('hey, error when loading names list - ' + err),
() => {
// Dismiss the popup because data is ready
this.loading.dismiss();
console.info('loading Jsons complete')}
)
}
The solution is don't do this in the constructor, use ngOnInit() instead. Components are created only once, therefore the constructor will only be called when first created.
Your component class must implement the OnInit interface:
import { Component, OnInit } from '#angular/core';
#Component({
templateUrl: '...',
})
export class ListOfNames implements OnInit {
constructor(...)
ngOnInit() {
this.loadJsons();
}
private loadJsons() {
...
}
}
i'm coming from Angular 2 world, not ionic, but angular 2 has the option to register callbacks on init/destory (ngInit/ngDestory).
try to move initialization to ngInit, save subscription handler, and don't forget to unsubscribe it on destory.
i think your issue related to that you are not unsubscribing.. :\
I'm working on a project that's using typescript and angular to generate dynamic web content. Currently, my job is to format an input field as currency, which is simple enough in plain javascript (with angular), but I need to do it in typescript, which is presenting some problems. Here's my directive (which I formatted as a class as per another stack overflow answer suggested)
namespace incode.directives.label {
interface IScope extends ng.IScope {
amount: number;
}
export class IncodeCurrencyInputDirective implements ng.IDirective {
restrict ='A';
public require: 'ngModel';
public scope: Object;
replace = true;
public link: ng.IDirectiveLinkFn | ng.IDirectivePrePost;
constructor(private $filter: ng.IFilterService) {
this.scope = {
amount: '='
};
this.link = (scope: IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ctlr: any, $filter: ng.IFilterService) => {
//element.bind('focus',
// function () {
// element.val(scope.amount);
// });
element.bind('input',
function () {
scope.amount = element.val();
scope.$apply;
});
element.bind('blur',
function () {
element.val($filter('currency')(scope.amount));
});
}
}
public static factory(): ng.IDirectiveFactory {
var directive = ($filter) => new IncodeCurrencyInputDirective($filter);
directive.$inject = ['$filter'];
return directive;
}
}
angular.module('incode.module')
.directive('ixCurrency', incode.directives.label.IncodeCurrencyInputDirective.factory());
}
and here's the template that uses it
<md-input-container layout-fill class="number-range">
<input placeholder="From"
type="text"
name="from"
ix-currency
amount="0.00"
precision="{{rangeController.precision}}"
ng-model="rangeController.Bucket.RangeFilterFromString"/>
</md-input-container>
As it stands at the moment, the script generates an error on blur: TypeError: cloneConnectFn is not a function which seems like a very low-level function used by angular, and shouldn't normally be producing this error. Any insight you can provide is greatly appreciated!
EDIT: Since I know a lot of angular gurus have never touched typescript, I'll attach a pastebin of the compiled javascript, in case that helps http://pastebin.com/NnTLqauL
I figured it out, no idea why it was giving me that error precisely, but the root of the problem is a miscommunication of scope between typescript and angular. You'll notice I have the line
element.val($filter('currency')(scope.amount));
in the 'blur' binding above. The reason I reference filter in this way is because if you write this.$filter, typescript compiles it such that even though it's inside an object, this refers to window instead of the object, so $filter is out of scope. The solution is to use a lambda function to spoof the scoping like so:
element.bind('blur',
() => {
element.val(this.$filter('currency')(scope.amount));
});
Always be mindful of where this points (something extremely difficult for me it seems) and you should be fine.
I am working on ionic application. I want to add multi-language support for the same using language .json files. So I googled it and found few examples for loading files using "angular static file loader plugin", but it loads all the language files at once.
My question is, can we load user selected language file only to avoid extra time for loading other language files. Can anybody please let me know how can I achieve it ?? or loading all files at a time is better approach ?? or is there any better implementation I can do ??
Thank you.
My question is, can we load user selected language file only to avoid extra time for loading other language files.
Yes.
Can anybody please let me know how can I achieve it ?? or loading all files at a time is better approach ?? or is there any better implementation I can do ??
Use angular-translate and angular-translate-loader-url
in your app.config()
$translateProvider.useUrlLoader('/translate');
$translateProvider.preferredLanguage('en_US');
This will be an equivalent of request /translate?lang=en_US to get the specific language (default) when your app initial.
Later when you want to change to another language:
// Example in one of your controller
$translate.use('fr_FR');
This will trigger a request /translate?lang=fr_FR to fetch another translation file
I have created JSON files of language as "en.json" in my project.. and I want to use that file.. so is it possible by using above code?
For JSON files, as mentioned in your question angular-translate-loader-static-files Will do the trick (I am not sure why you have all file loaded in once, because it supposed to be lazy loading)
$translateProvider.useStaticFilesLoader({
prefix: '',
suffix: '.json'
});
$translateProvider.preferredLanguage('en');
This will load en.json.
To lazy load another translation file.
$translate.uses('fr');
This will load fr.json
Best way to change language Globally is to use pipes and send the language parameter as an argument.
This would automatically change the Language across the components where the language pipe is utilized.
The following example can be used to supple multiple language at a time and can be used to change Language dynamically on a single click
// for example: **language.pipe.ts**
import { Pipe, PipeTransform, OnInit, OnChanges } from '#angular/core';
import { LanguageService } from '../services/language.service';
#Pipe({
name: 'language'
})
export class LanguagePipe implements PipeTransform {
constructor(
public lang: LanguageService
) { }
transform(value: string, args?: any): any {
return this.lang.getLang(value);
}
}
// **language.service.ts**
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
#Injectable()
export class LanguageService {
selectedLanguage = 'ch';
segmentsNames: any = {};
constantSegmentNames: any = {};
language = {
undefined: { 'en': '', 'ch': '' },
null: { 'en': '', 'ch': '' },
'': { 'en': '', 'ch': '' },
'hello': { 'en': 'Hello', 'ch': '你好' },
'world': { 'en': 'World', 'ch': '世界' }
};
constructor(private _http: HttpClient) { }
getLang(value: string, args?: any): any {
if (this.language[value]) {
return this.language[value][this.selectedLanguage];
}
return value;
}
/**
* #function that is used to toggle the selected language among 'en' and 'ch'
*/
changeLanguage() {
if (this.selectedLanguage === 'en') {
this.selectedLanguage = 'ch';
} else {
this.selectedLanguage = 'en';
}
}
}
// **Use Language Pipe in HTML AS**
<strong>{{'hello' | language:lang.selectedLanguage}}{{'world' | language:lang.selectedLanguage}}</strong>
PS: Don't forget to import the pipe & service in all the components where you want to use this functionality
Conslusion: you can write your own logic to fetch the appropriate Language.json file based on user selected language and use it in the service above mentioned